Last active
September 14, 2026 14:21
-
-
Save HDCharles/51c392795d109078cb89b2f45ccfa11c to your computer and use it in GitHub Desktop.
mse_bench.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| """ | |
| Benchmark for MSE observer grid search. (results at bottom) | |
| Profiles the grid search that finds optimal min/max ranges for quantization. | |
| The hot path is `_calculate_error` (called once per shrink step: default 20 | |
| steps), which runs `calculate_qparams` + `fake_quantize` on the full weight | |
| tensor each time. | |
| Usage: | |
| python benchmarks/bench_mse_observer.py [--device cuda] [--rows 4096] [--cols 4096] | |
| """ | |
| import argparse | |
| import time | |
| import torch | |
| import triton | |
| import triton.language as tl | |
| from compressed_tensors.quantization import ( | |
| QuantizationArgs, | |
| QuantizationScheme, | |
| QuantizationStrategy, | |
| QuantizationType, | |
| ) | |
| from compressed_tensors.quantization.lifecycle import fake_quantize | |
| from compressed_tensors.quantization.utils import calculate_qparams, calculate_range | |
| from llmcompressor.modifiers.quantization.calibration import ( | |
| initialize_observer, | |
| observe, | |
| ) | |
| from llmcompressor.observers.helpers import flatten_for_calibration | |
| WARMUP = 2 | |
| ITERS = 5 | |
| # Configured by CLI in main(). Kept separate from the common benchmark function | |
| # signature so the existing variants remain directly comparable. | |
| HIER_CONFIG = { | |
| "n": 16, | |
| "keep_num": 1, | |
| "keep_den": 4, | |
| "m": 2, | |
| "k": 3, | |
| "p_min": 0.8, | |
| "p_max": 1.8, | |
| "fast_pow": False, | |
| } | |
| # ── Inlined grid search variants ────────────────────────────────────────────── | |
| def grid_search_eager( | |
| observed, args, token_args, maxshrink, patience, grid, norm, chunk_size | |
| ): | |
| """Eager grid search — direct port from mse_quant.py.""" | |
| min_val = torch.amin(observed, dim=(0, -1)) | |
| max_val = torch.amax(observed, dim=(0, -1)) | |
| best_error = torch.full_like(min_val, torch.finfo(min_val.dtype).max) | |
| best_min_val = min_val.clone() | |
| best_max_val = max_val.clone() | |
| total_steps = int(maxshrink * grid) | |
| no_improve_count = 0 | |
| for i in range(total_steps): | |
| p = 1 - i / grid | |
| shrinked_min = min_val * p | |
| shrinked_max = max_val * p | |
| candidate_scales, candidate_zero_points = calculate_qparams( | |
| min_vals=shrinked_min, | |
| max_vals=shrinked_max, | |
| quantization_args=args, | |
| global_scale=None, | |
| ) | |
| q = fake_quantize( | |
| observed, | |
| candidate_scales.unsqueeze(-1), | |
| candidate_zero_points.unsqueeze(-1), | |
| token_args, | |
| ).to(observed.dtype) | |
| err = torch.sum((q - observed).abs().pow(norm), dim=(0, -1)) | |
| del q | |
| improved = err < best_error | |
| if torch.any(improved): | |
| best_error[improved] = err[improved] | |
| best_min_val[improved] = shrinked_min[improved] | |
| best_max_val[improved] = shrinked_max[improved] | |
| no_improve_count = 0 | |
| else: | |
| no_improve_count += 1 | |
| if no_improve_count >= patience: | |
| break | |
| return best_min_val, best_max_val | |
| def grid_search_eager_no_patience( | |
| observed, args, token_args, maxshrink, patience, grid, norm, chunk_size | |
| ): | |
| """Eager grid search that always evaluates the complete candidate grid.""" | |
| del patience | |
| total_steps = int(maxshrink * grid) | |
| return grid_search_eager( | |
| observed, | |
| args, | |
| token_args, | |
| maxshrink, | |
| total_steps + 1, | |
| grid, | |
| norm, | |
| chunk_size, | |
| ) | |
| @torch.compile(dynamic=True) | |
| def _compute_chunk_compiled( | |
| observed, | |
| args, | |
| token_args, | |
| min_val, | |
| max_val, | |
| ps, | |
| chunk_size, | |
| norm, | |
| best_error, | |
| best_min_val, | |
| best_max_val, | |
| ): | |
| for j in range(chunk_size): | |
| shrinked_min = min_val * ps[j] | |
| shrinked_max = max_val * ps[j] | |
| candidate_scales, candidate_zero_points = calculate_qparams( | |
| min_vals=shrinked_min, | |
| max_vals=shrinked_max, | |
| quantization_args=args, | |
| global_scale=None, | |
| ) | |
| q = fake_quantize( | |
| observed, | |
| candidate_scales.unsqueeze(-1), | |
| candidate_zero_points.unsqueeze(-1), | |
| token_args, | |
| ).to(observed.dtype) | |
| err = torch.sum((q - observed).abs().pow(norm), dim=(0, -1)) | |
| del q | |
| improved = err < best_error | |
| best_error = torch.where(improved, err, best_error) | |
| best_min_val = torch.where(improved, shrinked_min, best_min_val) | |
| best_max_val = torch.where(improved, shrinked_max, best_max_val) | |
| return best_error, best_min_val, best_max_val | |
| def grid_search_compiled( | |
| observed, args, token_args, maxshrink, patience, grid, norm, chunk_size | |
| ): | |
| """Compiled grid search — chunked torch.compile inner loop.""" | |
| import torch._dynamo.config | |
| import torch._dynamo.decorators | |
| torch._dynamo.config.capture_scalar_outputs = True | |
| min_val = torch.amin(observed, dim=(0, -1)) | |
| max_val = torch.amax(observed, dim=(0, -1)) | |
| best_error = torch.full_like(min_val, torch.finfo(min_val.dtype).max) | |
| best_min_val = min_val.clone() | |
| best_max_val = max_val.clone() | |
| total_steps = int(maxshrink * grid) | |
| no_improve_count = 0 | |
| observed = observed.clone() | |
| torch._dynamo.decorators.mark_unbacked(observed, observed.ndim - 1) | |
| idx = 0 | |
| while idx < total_steps: | |
| chunk_end = min(idx + chunk_size, total_steps) | |
| current_chunk = chunk_end - idx | |
| ps = torch.tensor( | |
| [1.0 - (idx + j) / grid for j in range(current_chunk)], | |
| dtype=observed.dtype, | |
| device=observed.device, | |
| ) | |
| prev_best = best_error.clone() | |
| best_error, best_min_val, best_max_val = _compute_chunk_compiled( | |
| observed, | |
| args, | |
| token_args, | |
| min_val, | |
| max_val, | |
| ps, | |
| current_chunk, | |
| norm, | |
| best_error, | |
| best_min_val, | |
| best_max_val, | |
| ) | |
| if torch.equal(prev_best, best_error): | |
| no_improve_count += current_chunk | |
| if no_improve_count >= patience: | |
| break | |
| else: | |
| no_improve_count = 0 | |
| idx = chunk_end | |
| return best_min_val, best_max_val | |
| # ── Triton fused implementation ─────────────────────────────────────────────── | |
| @triton.jit | |
| def _fused_grid_search_kernel( | |
| observed_ptr, | |
| all_scales_ptr, | |
| all_zps_ptr, | |
| best_step_ptr, | |
| best_error_ptr, | |
| num_rows, | |
| num_groups, | |
| group_size, | |
| total_steps, | |
| stride_obs_row, | |
| stride_obs_group, | |
| stride_scale_step, | |
| stride_scale_row, | |
| q_min: tl.constexpr, | |
| q_max: tl.constexpr, | |
| norm, | |
| BLOCK_G: tl.constexpr, | |
| TOTAL_STEPS: tl.constexpr, | |
| ): | |
| """Precomputed-qparam family: serial exhaustive search, one program/group. | |
| Unlike the GPTQ-arithmetic variants, this uses the original simple | |
| clamp/round/dequant path. Every candidate scale and zero point is computed | |
| by PyTorch before launch, then grid steps are scanned serially in-kernel. | |
| """ | |
| pid = tl.program_id(0) | |
| row = pid // num_groups | |
| group = pid % num_groups | |
| if row >= num_rows: | |
| return | |
| g_idx = tl.arange(0, BLOCK_G) | |
| g_mask = g_idx < group_size | |
| obs = tl.load( | |
| observed_ptr + row * stride_obs_row + group * stride_obs_group + g_idx, | |
| mask=g_mask, | |
| other=0.0, | |
| ) | |
| best_err = float("inf") | |
| best_s = 0 | |
| for step in range(TOTAL_STEPS): | |
| if step < total_steps: | |
| scale = tl.load( | |
| all_scales_ptr | |
| + step * stride_scale_step | |
| + row * stride_scale_row | |
| + group | |
| ) | |
| zp = tl.load( | |
| all_zps_ptr + step * stride_scale_step + row * stride_scale_row + group | |
| ) | |
| # fused fake_quantize: round(x/scale + zp), clamp, dequant | |
| q_float = obs / scale + zp | |
| q_int = tl.extra.cuda.libdevice.nearbyint(q_float) | |
| q_int = tl.minimum(tl.maximum(q_int, q_min), q_max) | |
| q = (q_int - zp) * scale | |
| diff = tl.abs(q - obs) | |
| norm_f = norm.to(tl.float32) | |
| diff_pow = tl.extra.cuda.libdevice.pow(diff, norm_f) | |
| err = tl.sum(tl.where(g_mask, diff_pow, 0.0), axis=0) | |
| is_better = err < best_err | |
| best_err = tl.where(is_better, err, best_err) | |
| best_s = tl.where(is_better, step, best_s) | |
| tl.store(best_step_ptr + row * num_groups + group, best_s) | |
| tl.store(best_error_ptr + row * num_groups + group, best_err) | |
| def grid_search_triton( | |
| observed, args, token_args, maxshrink, patience, grid, norm, chunk_size | |
| ): | |
| """Fused Triton grid search — one kernel per (row, group), all steps in registers.""" | |
| min_val = torch.amin(observed, dim=(0, -1)) | |
| max_val = torch.amax(observed, dim=(0, -1)) | |
| total_steps = int(maxshrink * grid) | |
| # Precompute all candidate scales and zero_points | |
| all_scales = [] | |
| all_zps = [] | |
| for i in range(total_steps): | |
| p = 1 - i / grid | |
| s, zp = calculate_qparams( | |
| min_vals=min_val * p, | |
| max_vals=max_val * p, | |
| quantization_args=args, | |
| global_scale=None, | |
| ) | |
| all_scales.append(s) | |
| all_zps.append(zp) | |
| all_scales = torch.stack(all_scales).contiguous() # (total_steps, *qparam_shape) | |
| all_zps = torch.stack(all_zps).to(dtype=torch.float32).contiguous() | |
| _, num_rows, num_groups, group_size = observed.shape | |
| best_step = torch.zeros( | |
| num_rows, num_groups, dtype=torch.int32, device=observed.device | |
| ) | |
| best_error = torch.full( | |
| (num_rows, num_groups), | |
| float("inf"), | |
| device=observed.device, | |
| dtype=torch.float32, | |
| ) | |
| observed_contig = observed.contiguous() | |
| BLOCK_G = triton.next_power_of_2(group_size) | |
| TOTAL_STEPS = triton.next_power_of_2(total_steps) | |
| num_programs = num_rows * num_groups | |
| grid_launch = (num_programs,) | |
| bit_range = 2**args.num_bits - 1 | |
| if args.symmetric: | |
| q_min_val = -(2 ** (args.num_bits - 1)) | |
| q_max_val = 2 ** (args.num_bits - 1) - 1 | |
| else: | |
| q_min_val = 0 | |
| q_max_val = 2**args.num_bits - 1 | |
| _fused_grid_search_kernel[grid_launch]( | |
| observed_contig, | |
| all_scales, | |
| all_zps, | |
| best_step, | |
| best_error, | |
| num_rows, | |
| num_groups, | |
| group_size, | |
| total_steps, | |
| observed_contig.stride(1), | |
| observed_contig.stride(2), | |
| all_scales.stride(0), | |
| all_scales.stride(1), | |
| float(q_min_val), | |
| float(q_max_val), | |
| norm, | |
| BLOCK_G=BLOCK_G, | |
| TOTAL_STEPS=TOTAL_STEPS, | |
| ) | |
| # Reconstruct best_min/max from best step indices | |
| ps = torch.tensor( | |
| [1.0 - i / grid for i in range(total_steps)], | |
| dtype=min_val.dtype, | |
| device=min_val.device, | |
| ) | |
| best_p = ps[best_step.long()] | |
| best_min_val = min_val * best_p | |
| best_max_val = max_val * best_p | |
| return best_min_val, best_max_val | |
| @triton.jit | |
| def _gptq_quantize_dequantize( | |
| values, | |
| scale, | |
| zp, | |
| q_min, | |
| q_max, | |
| QUANT_TYPE: tl.constexpr, | |
| DEQUANT_DTYPE: tl.constexpr, | |
| HAS_ZP: tl.constexpr, | |
| ): | |
| """GPTQ-family inline utility implementing its exact QDQ arithmetic. | |
| Shared by exhaustive, patience, vectorized, and hierarchical GPTQ kernels; | |
| this is an inlined helper rather than an independently launched kernel. | |
| """ | |
| normalized = tl.extra.cuda.libdevice.div_rn(values, scale) | |
| if HAS_ZP: | |
| normalized = normalized + zp | |
| clamped = tl.clamp(normalized, q_min, q_max) | |
| if QUANT_TYPE == 0: | |
| rounded = tl.extra.cuda.libdevice.rint(clamped) | |
| elif QUANT_TYPE == 1: | |
| absolute = tl.abs(clamped) | |
| magnitude = tl.where( | |
| absolute <= 0.25, | |
| 0.0, | |
| tl.where( | |
| absolute < 0.75, | |
| 0.5, | |
| tl.where( | |
| absolute <= 1.25, | |
| 1.0, | |
| tl.where( | |
| absolute < 1.75, | |
| 1.5, | |
| tl.where( | |
| absolute <= 2.5, | |
| 2.0, | |
| tl.where( | |
| absolute < 3.5, | |
| 3.0, | |
| tl.where(absolute <= 5.0, 4.0, 6.0), | |
| ), | |
| ), | |
| ), | |
| ), | |
| ), | |
| ) | |
| rounded = tl.where(clamped < 0.0, -magnitude, magnitude) | |
| else: | |
| rounded = clamped.to(tl.float8e4nv).to(tl.float32) | |
| if DEQUANT_DTYPE == 1: | |
| rounded = rounded.to(tl.bfloat16) | |
| scale_value = scale.to(tl.bfloat16) | |
| if HAS_ZP: | |
| rounded = (rounded - zp.to(tl.bfloat16)).to(tl.bfloat16) | |
| quantized = (rounded * scale_value).to(tl.bfloat16).to(tl.float32) | |
| elif DEQUANT_DTYPE == 2: | |
| rounded = rounded.to(tl.float16) | |
| scale_value = scale.to(tl.float16) | |
| if HAS_ZP: | |
| rounded = (rounded - zp.to(tl.float16)).to(tl.float16) | |
| quantized = (rounded * scale_value).to(tl.float16).to(tl.float32) | |
| else: | |
| if HAS_ZP: | |
| quantized = tl.extra.cuda.libdevice.mul_rn( | |
| tl.extra.cuda.libdevice.sub_rn(rounded, zp), scale | |
| ) | |
| else: | |
| quantized = tl.extra.cuda.libdevice.mul_rn(rounded, scale) | |
| return quantized | |
| @triton.jit | |
| def _fused_grid_search_gptq_quant_kernel( | |
| observed_ptr, | |
| all_scales_ptr, | |
| all_zps_ptr, | |
| best_step_ptr, | |
| best_error_ptr, | |
| num_rows, | |
| num_groups, | |
| group_size, | |
| total_steps, | |
| stride_obs_row, | |
| stride_obs_group, | |
| stride_scale_step, | |
| stride_scale_row, | |
| q_min, | |
| q_max, | |
| norm, | |
| patience, | |
| BLOCK_G: tl.constexpr, | |
| TOTAL_STEPS: tl.constexpr, | |
| QUANT_TYPE: tl.constexpr, | |
| DEQUANT_DTYPE: tl.constexpr, | |
| OBSERVED_DTYPE: tl.constexpr, | |
| HAS_ZP: tl.constexpr, | |
| USE_PATIENCE: tl.constexpr, | |
| ): | |
| """Precomputed-qparam GPTQ family: serial search, optionally with patience. | |
| The quantize/dequantize sequence intentionally mirrors the fused GPTQ | |
| kernel rather than relying on Triton's default arithmetic: | |
| normalized = div_rn(x, scale) + zero_point | |
| rounded = rint(clamp(normalized)) | |
| dequantized = mul_rn(rounded - zero_point, scale) | |
| ``QUANT_TYPE`` additionally supports the FP4 E2M1 and FP8 E4M3 paths | |
| used by the GPTQ kernel. The grid-search reduction is otherwise the | |
| same as ``_fused_grid_search_kernel``. Unlike the base-scale family, all | |
| candidate qparams are materialized before launch; unlike the vectorized | |
| sibling, steps are serial so per-group patience can skip trailing work. | |
| """ | |
| pid = tl.program_id(0) | |
| row = pid // num_groups | |
| group = pid % num_groups | |
| if row >= num_rows: | |
| return | |
| g_idx = tl.arange(0, BLOCK_G) | |
| g_mask = g_idx < group_size | |
| obs = tl.load( | |
| observed_ptr + row * stride_obs_row + group * stride_obs_group + g_idx, | |
| mask=g_mask, | |
| other=0.0, | |
| ).to(tl.float32) | |
| if OBSERVED_DTYPE == 1: | |
| best_err = tl.full([], float("inf"), dtype=tl.bfloat16) | |
| elif OBSERVED_DTYPE == 2: | |
| best_err = tl.full([], float("inf"), dtype=tl.float16) | |
| else: | |
| best_err = tl.full([], float("inf"), dtype=tl.float32) | |
| best_s = 0 | |
| patience_ctr = 0 | |
| for step in range(TOTAL_STEPS): | |
| if step < total_steps: | |
| active = 1 | |
| if USE_PATIENCE: | |
| active = patience_ctr < patience | |
| if active: | |
| scale = tl.load( | |
| all_scales_ptr | |
| + step * stride_scale_step | |
| + row * stride_scale_row | |
| + group | |
| ).to(tl.float32) | |
| scale = tl.maximum(scale, 1.1754943508222875e-38) | |
| zp = scale * 0.0 | |
| if HAS_ZP: | |
| zp = tl.load( | |
| all_zps_ptr | |
| + step * stride_scale_step | |
| + row * stride_scale_row | |
| + group | |
| ).to(tl.float32) | |
| quantized = _gptq_quantize_dequantize( | |
| obs, | |
| scale, | |
| zp, | |
| q_min, | |
| q_max, | |
| QUANT_TYPE=QUANT_TYPE, | |
| DEQUANT_DTYPE=DEQUANT_DTYPE, | |
| HAS_ZP=HAS_ZP, | |
| ) | |
| if OBSERVED_DTYPE == 1: | |
| obs_for_error = obs.to(tl.bfloat16) | |
| quantized = quantized.to(tl.bfloat16) | |
| elif OBSERVED_DTYPE == 2: | |
| obs_for_error = obs.to(tl.float16) | |
| quantized = quantized.to(tl.float16) | |
| else: | |
| obs_for_error = obs | |
| diff = tl.abs(quantized - obs_for_error).to(tl.float32) | |
| norm_f = norm.to(tl.float32) | |
| diff_pow = tl.extra.cuda.libdevice.pow(diff, norm_f) | |
| if OBSERVED_DTYPE == 1: | |
| diff_pow = diff_pow.to(tl.bfloat16) | |
| elif OBSERVED_DTYPE == 2: | |
| diff_pow = diff_pow.to(tl.float16) | |
| err = tl.sum(tl.where(g_mask, diff_pow, 0.0), axis=0).to(best_err.dtype) | |
| is_better = err < best_err | |
| best_err = tl.where(is_better, err, best_err) | |
| best_s = tl.where(is_better, step, best_s) | |
| if USE_PATIENCE: | |
| patience_ctr = tl.where(is_better, 0, patience_ctr + 1) | |
| tl.store(best_step_ptr + row * num_groups + group, best_s) | |
| tl.store(best_error_ptr + row * num_groups + group, best_err) | |
| @triton.jit | |
| def _round_candidate_scale( | |
| scale, | |
| SCALE_ROUND_TYPE: tl.constexpr, | |
| QUANT_TYPE: tl.constexpr, | |
| ): | |
| """Base-scale-family inline utility matching qparam scale rounding. | |
| It lets the patience kernel derive qparams without precomputing every grid | |
| point, including float16, bfloat16, FP8, and MX E8M0 scale encodings. | |
| """ | |
| if SCALE_ROUND_TYPE == 1: | |
| return tl.minimum(scale, 65504.0).to(tl.float16).to(tl.float32) | |
| if SCALE_ROUND_TYPE == 2: | |
| return scale.to(tl.bfloat16).to(tl.float32) | |
| if SCALE_ROUND_TYPE == 3: | |
| return tl.minimum(scale, 448.0).to(tl.float8e4nv).to(tl.float32) | |
| if SCALE_ROUND_TYPE == 4: | |
| # MX E8M0: round the group maximum to a power of two, shift it by | |
| # the element format's exponent offset, encode, then decode to the | |
| # effective floating-point scale consumed by QDQ. | |
| is_zero = scale == 0.0 | |
| safe_scale = tl.where(is_zero, 1.0, scale) | |
| exponent = tl.floor(tl.extra.cuda.libdevice.log2(safe_scale)) | |
| power_2 = tl.extra.cuda.libdevice.exp2(exponent) | |
| exponent += tl.where(safe_scale >= 1.75 * power_2, 1.0, 0.0) | |
| element_offset = 2.0 if QUANT_TYPE == 1 else 8.0 | |
| encoded = tl.clamp(exponent - element_offset + 127.0, 0.0, 255.0) | |
| encoded = tl.where(is_zero, 0.0, encoded) | |
| return tl.extra.cuda.libdevice.exp2(encoded - 127.0) | |
| return scale | |
| @triton.jit | |
| def _base_scale_grid_search_gptq_quant_exhaustive_kernel( | |
| observed_ptr, | |
| scale_base_ptr, | |
| zp_base_ptr, | |
| best_step_ptr, | |
| best_error_ptr, | |
| num_rows, | |
| num_groups, | |
| group_size, | |
| stride_obs_row, | |
| stride_obs_group, | |
| stride_scale_row, | |
| inv_grid, | |
| q_min, | |
| q_max, | |
| norm, | |
| scale_eps, | |
| BLOCK_G: tl.constexpr, | |
| TOTAL_STEPS: tl.constexpr, | |
| QUANT_TYPE: tl.constexpr, | |
| DEQUANT_DTYPE: tl.constexpr, | |
| OBSERVED_DTYPE: tl.constexpr, | |
| HAS_ZP: tl.constexpr, | |
| ): | |
| """Base-scale GPTQ family: fully unrolled exhaustive search. | |
| Candidate scales are derived in-kernel from one qparam per group. This is | |
| the lean exhaustive sibling: exact compile-time step count, no padded loop, | |
| activity mask, patience counter, scale rounding, or stopping bookkeeping. | |
| """ | |
| pid = tl.program_id(0) | |
| row = pid // num_groups | |
| group = pid % num_groups | |
| g_idx = tl.arange(0, BLOCK_G) | |
| g_mask = g_idx < group_size | |
| obs = tl.load( | |
| observed_ptr + row * stride_obs_row + group * stride_obs_group + g_idx, | |
| mask=g_mask, | |
| other=0.0, | |
| ).to(tl.float32) | |
| qparam_offset = row * stride_scale_row + group | |
| scale_base = tl.load(scale_base_ptr + qparam_offset).to(tl.float32) | |
| zp = scale_base * 0.0 | |
| if HAS_ZP: | |
| zp = tl.load(zp_base_ptr + qparam_offset).to(tl.float32) | |
| if OBSERVED_DTYPE == 1: | |
| best_err = tl.full([], float("inf"), dtype=tl.bfloat16) | |
| elif OBSERVED_DTYPE == 2: | |
| best_err = tl.full([], float("inf"), dtype=tl.float16) | |
| else: | |
| best_err = tl.full([], float("inf"), dtype=tl.float32) | |
| best_s = 0 | |
| inv_grid_f = inv_grid.to(tl.float32) | |
| norm_f = norm.to(tl.float32) | |
| for step in tl.static_range(0, TOTAL_STEPS): | |
| p = (1.0 - step * inv_grid_f).to(tl.float32) | |
| scale = tl.maximum(scale_base * p, scale_eps) | |
| quantized = _gptq_quantize_dequantize( | |
| obs, | |
| scale, | |
| zp, | |
| q_min, | |
| q_max, | |
| QUANT_TYPE=QUANT_TYPE, | |
| DEQUANT_DTYPE=DEQUANT_DTYPE, | |
| HAS_ZP=HAS_ZP, | |
| ) | |
| if OBSERVED_DTYPE == 1: | |
| obs_for_error = obs.to(tl.bfloat16) | |
| quantized = quantized.to(tl.bfloat16) | |
| elif OBSERVED_DTYPE == 2: | |
| obs_for_error = obs.to(tl.float16) | |
| quantized = quantized.to(tl.float16) | |
| else: | |
| obs_for_error = obs | |
| diff = tl.abs(quantized - obs_for_error).to(tl.float32) | |
| diff_pow = tl.extra.cuda.libdevice.pow(diff, norm_f) | |
| if OBSERVED_DTYPE == 1: | |
| diff_pow = diff_pow.to(tl.bfloat16) | |
| elif OBSERVED_DTYPE == 2: | |
| diff_pow = diff_pow.to(tl.float16) | |
| err = tl.sum(tl.where(g_mask, diff_pow, 0.0), axis=0).to(best_err.dtype) | |
| is_better = err < best_err | |
| best_err = tl.where(is_better, err, best_err) | |
| best_s = tl.where(is_better, step, best_s) | |
| tl.store(best_step_ptr + row * num_groups + group, best_s) | |
| tl.store(best_error_ptr + row * num_groups + group, best_err) | |
| @triton.jit | |
| def _grid_major_base_scale_gptq_quant_kernel( | |
| observed_ptr, | |
| scale_base_ptr, | |
| zp_base_ptr, | |
| best_step_ptr, | |
| best_error_ptr, | |
| num_rows, | |
| num_groups, | |
| group_size, | |
| stride_obs_row, | |
| stride_obs_group, | |
| stride_scale_row, | |
| step, | |
| inv_grid, | |
| q_min, | |
| q_max, | |
| norm, | |
| scale_eps, | |
| BLOCK_G: tl.constexpr, | |
| TILE_GROUPS: tl.constexpr, | |
| QUANT_TYPE: tl.constexpr, | |
| DEQUANT_DTYPE: tl.constexpr, | |
| OBSERVED_DTYPE: tl.constexpr, | |
| HAS_ZP: tl.constexpr, | |
| ): | |
| """One candidate per launch, with one or more qparam groups per program. | |
| This is the grid-major counterpart to the serial base-scale kernel: all | |
| groups evaluate candidate ``step`` before the host launches the next grid | |
| point. ``TILE_GROUPS`` controls the qparam-group tile. In particular, | |
| using ``128 // BLOCK_G`` gives eight 16-value NVFP4 groups per program and | |
| one 128-value group per program. | |
| """ | |
| pid = tl.program_id(0) | |
| linear_groups = pid * TILE_GROUPS + tl.arange(0, TILE_GROUPS) | |
| row = linear_groups // num_groups | |
| group = linear_groups % num_groups | |
| group_mask = linear_groups < num_rows * num_groups | |
| group_idx = tl.arange(0, BLOCK_G) | |
| value_mask = group_mask[:, None] & (group_idx[None, :] < group_size) | |
| obs = tl.load( | |
| observed_ptr | |
| + row[:, None] * stride_obs_row | |
| + group[:, None] * stride_obs_group | |
| + group_idx[None, :], | |
| mask=value_mask, | |
| other=0.0, | |
| ).to(tl.float32) | |
| qparam_offsets = row * stride_scale_row + group | |
| scale_base = tl.load(scale_base_ptr + qparam_offsets, mask=group_mask, other=1.0) | |
| scale = tl.maximum(scale_base.to(tl.float32) * (1.0 - step * inv_grid), scale_eps)[ | |
| :, None | |
| ] | |
| zp = scale * 0.0 | |
| if HAS_ZP: | |
| zp = tl.load(zp_base_ptr + qparam_offsets, mask=group_mask, other=0.0).to( | |
| tl.float32 | |
| )[:, None] | |
| quantized = _gptq_quantize_dequantize( | |
| obs, | |
| scale, | |
| zp, | |
| q_min, | |
| q_max, | |
| QUANT_TYPE=QUANT_TYPE, | |
| DEQUANT_DTYPE=DEQUANT_DTYPE, | |
| HAS_ZP=HAS_ZP, | |
| ) | |
| if OBSERVED_DTYPE == 1: | |
| obs_for_error = obs.to(tl.bfloat16) | |
| quantized = quantized.to(tl.bfloat16) | |
| elif OBSERVED_DTYPE == 2: | |
| obs_for_error = obs.to(tl.float16) | |
| quantized = quantized.to(tl.float16) | |
| else: | |
| obs_for_error = obs | |
| diff = tl.abs(quantized - obs_for_error).to(tl.float32) | |
| diff_pow = tl.extra.cuda.libdevice.pow(diff, norm.to(tl.float32)) | |
| if OBSERVED_DTYPE == 1: | |
| diff_pow = diff_pow.to(tl.bfloat16) | |
| elif OBSERVED_DTYPE == 2: | |
| diff_pow = diff_pow.to(tl.float16) | |
| err = tl.sum(tl.where(value_mask, diff_pow, 0.0), axis=1) | |
| old_error = tl.load( | |
| best_error_ptr + linear_groups, mask=group_mask, other=float("inf") | |
| ) | |
| is_better = err < old_error | |
| tl.store( | |
| best_error_ptr + linear_groups, | |
| tl.where(is_better, err, old_error), | |
| mask=group_mask, | |
| ) | |
| tl.store( | |
| best_step_ptr + linear_groups, | |
| tl.where(is_better, step, 0), | |
| mask=group_mask & is_better, | |
| ) | |
| @triton.jit | |
| def _base_scale_grid_search_gptq_quant_exhaustive_tile128_kernel( | |
| observed_ptr, | |
| scale_base_ptr, | |
| zp_base_ptr, | |
| best_step_ptr, | |
| best_error_ptr, | |
| num_rows, | |
| num_groups, | |
| group_size, | |
| stride_obs_row, | |
| stride_obs_group, | |
| stride_scale_row, | |
| inv_grid, | |
| q_min, | |
| q_max, | |
| norm, | |
| scale_eps, | |
| BLOCK_G: tl.constexpr, | |
| TILE_GROUPS: tl.constexpr, | |
| TOTAL_STEPS: tl.constexpr, | |
| QUANT_TYPE: tl.constexpr, | |
| DEQUANT_DTYPE: tl.constexpr, | |
| OBSERVED_DTYPE: tl.constexpr, | |
| HAS_ZP: tl.constexpr, | |
| ): | |
| """Serial exhaustive base-scale search, tiled to a fixed value budget. | |
| This retains the existing base kernel's group-major algorithm (every | |
| program evaluates the full grid), but gives each program approximately 128 | |
| value lanes. With group size 16, a program owns eight groups; with group | |
| size 128, it owns one group. | |
| """ | |
| pid = tl.program_id(0) | |
| linear_groups = pid * TILE_GROUPS + tl.arange(0, TILE_GROUPS) | |
| row = linear_groups // num_groups | |
| group = linear_groups % num_groups | |
| group_mask = linear_groups < num_rows * num_groups | |
| group_idx = tl.arange(0, BLOCK_G) | |
| value_mask = group_mask[:, None] & (group_idx[None, :] < group_size) | |
| obs = tl.load( | |
| observed_ptr | |
| + row[:, None] * stride_obs_row | |
| + group[:, None] * stride_obs_group | |
| + group_idx[None, :], | |
| mask=value_mask, | |
| other=0.0, | |
| ).to(tl.float32) | |
| qparam_offsets = row * stride_scale_row + group | |
| scale_base = tl.load(scale_base_ptr + qparam_offsets, mask=group_mask, other=1.0) | |
| zp = scale_base * 0.0 | |
| if HAS_ZP: | |
| zp = tl.load(zp_base_ptr + qparam_offsets, mask=group_mask, other=0.0).to( | |
| tl.float32 | |
| ) | |
| best_err = tl.full((TILE_GROUPS,), float("inf"), tl.float32) | |
| best_step = tl.zeros((TILE_GROUPS,), tl.int32) | |
| for step in tl.static_range(0, TOTAL_STEPS): | |
| scale = tl.maximum( | |
| scale_base.to(tl.float32)[:, None] * (1.0 - step * inv_grid), | |
| scale_eps, | |
| ) | |
| quantized = _gptq_quantize_dequantize( | |
| obs, | |
| scale, | |
| zp[:, None], | |
| q_min, | |
| q_max, | |
| QUANT_TYPE=QUANT_TYPE, | |
| DEQUANT_DTYPE=DEQUANT_DTYPE, | |
| HAS_ZP=HAS_ZP, | |
| ) | |
| if OBSERVED_DTYPE == 1: | |
| obs_for_error = obs.to(tl.bfloat16) | |
| quantized = quantized.to(tl.bfloat16) | |
| elif OBSERVED_DTYPE == 2: | |
| obs_for_error = obs.to(tl.float16) | |
| quantized = quantized.to(tl.float16) | |
| else: | |
| obs_for_error = obs | |
| diff = tl.abs(quantized - obs_for_error).to(tl.float32) | |
| diff_pow = tl.extra.cuda.libdevice.pow(diff, norm.to(tl.float32)) | |
| if OBSERVED_DTYPE == 1: | |
| diff_pow = diff_pow.to(tl.bfloat16) | |
| elif OBSERVED_DTYPE == 2: | |
| diff_pow = diff_pow.to(tl.float16) | |
| err = tl.sum(tl.where(value_mask, diff_pow, 0.0), axis=1) | |
| is_better = err < best_err | |
| best_err = tl.where(is_better, err, best_err) | |
| best_step = tl.where(is_better, step, best_step) | |
| tl.store(best_step_ptr + linear_groups, best_step, mask=group_mask) | |
| tl.store(best_error_ptr + linear_groups, best_err, mask=group_mask) | |
| @triton.jit | |
| def _base_scale_grid_search_gptq_quant_patience_kernel( | |
| observed_ptr, | |
| scale_base_ptr, | |
| zp_base_ptr, | |
| best_step_ptr, | |
| best_error_ptr, | |
| num_rows, | |
| num_groups, | |
| group_size, | |
| total_steps, | |
| stride_obs_row, | |
| stride_obs_group, | |
| stride_scale_row, | |
| inv_grid, | |
| q_min, | |
| q_max, | |
| norm, | |
| patience, | |
| error_buffer, | |
| scale_eps, | |
| BLOCK_G: tl.constexpr, | |
| TOTAL_STEPS: tl.constexpr, | |
| QUANT_TYPE: tl.constexpr, | |
| DEQUANT_DTYPE: tl.constexpr, | |
| OBSERVED_DTYPE: tl.constexpr, | |
| HAS_ZP: tl.constexpr, | |
| SCALE_ROUND_TYPE: tl.constexpr, | |
| USE_PATIENCE: tl.constexpr, | |
| ): | |
| """Base-scale GPTQ family: serial search with per-group early stopping. | |
| Compared with the exhaustive sibling, this keeps a patience counter and can | |
| reset it for errors within ``error_buffer`` of the best. It also supports | |
| per-candidate scale-dtype rounding (including FP8 and E8M0 scale formats). | |
| """ | |
| pid = tl.program_id(0) | |
| row = pid // num_groups | |
| group = pid % num_groups | |
| if row >= num_rows: | |
| return | |
| g_idx = tl.arange(0, BLOCK_G) | |
| g_mask = g_idx < group_size | |
| obs = tl.load( | |
| observed_ptr + row * stride_obs_row + group * stride_obs_group + g_idx, | |
| mask=g_mask, | |
| other=0.0, | |
| ).to(tl.float32) | |
| qparam_offset = row * stride_scale_row + group | |
| scale_base = tl.load(scale_base_ptr + qparam_offset).to(tl.float32) | |
| zp = scale_base * 0.0 | |
| if HAS_ZP: | |
| zp = tl.load(zp_base_ptr + qparam_offset).to(tl.float32) | |
| if OBSERVED_DTYPE == 1: | |
| best_err = tl.full([], float("inf"), dtype=tl.bfloat16) | |
| elif OBSERVED_DTYPE == 2: | |
| best_err = tl.full([], float("inf"), dtype=tl.float16) | |
| else: | |
| best_err = tl.full([], float("inf"), dtype=tl.float32) | |
| best_s = 0 | |
| patience_ctr = 0 | |
| inv_grid_f = inv_grid.to(tl.float32) | |
| norm_f = norm.to(tl.float32) | |
| error_buffer_f = error_buffer.to(tl.float32) | |
| for step in range(TOTAL_STEPS): | |
| if step < total_steps: | |
| active = True | |
| if USE_PATIENCE: | |
| active = patience_ctr < patience | |
| if active: | |
| p = (1.0 - step * inv_grid_f).to(tl.float32) | |
| scale = _round_candidate_scale( | |
| scale_base * p, | |
| SCALE_ROUND_TYPE=SCALE_ROUND_TYPE, | |
| QUANT_TYPE=QUANT_TYPE, | |
| ) | |
| scale = tl.where(scale == 0.0, scale_eps, scale) | |
| scale = tl.maximum(scale, 1.1754943508222875e-38) | |
| quantized = _gptq_quantize_dequantize( | |
| obs, | |
| scale, | |
| zp, | |
| q_min, | |
| q_max, | |
| QUANT_TYPE=QUANT_TYPE, | |
| DEQUANT_DTYPE=DEQUANT_DTYPE, | |
| HAS_ZP=HAS_ZP, | |
| ) | |
| if OBSERVED_DTYPE == 1: | |
| obs_for_error = obs.to(tl.bfloat16) | |
| quantized = quantized.to(tl.bfloat16) | |
| elif OBSERVED_DTYPE == 2: | |
| obs_for_error = obs.to(tl.float16) | |
| quantized = quantized.to(tl.float16) | |
| else: | |
| obs_for_error = obs | |
| diff = tl.abs(quantized - obs_for_error).to(tl.float32) | |
| diff_pow = tl.extra.cuda.libdevice.pow(diff, norm_f) | |
| if OBSERVED_DTYPE == 1: | |
| diff_pow = diff_pow.to(tl.bfloat16) | |
| elif OBSERVED_DTYPE == 2: | |
| diff_pow = diff_pow.to(tl.float16) | |
| err = tl.sum(tl.where(g_mask, diff_pow, 0.0), axis=0).to(best_err.dtype) | |
| previous_best_err = best_err | |
| is_better = err < previous_best_err | |
| best_err = tl.where(is_better, err, best_err) | |
| best_s = tl.where(is_better, step, best_s) | |
| if USE_PATIENCE: | |
| within_buffer = err <= previous_best_err * (1.0 + error_buffer_f) | |
| patience_ctr = tl.where(within_buffer, 0, patience_ctr + 1) | |
| tl.store(best_step_ptr + row * num_groups + group, best_s) | |
| tl.store(best_error_ptr + row * num_groups + group, best_err) | |
| @triton.jit | |
| def _base_scale_grid_search_gptq_quant_patience_tiled_kernel( | |
| observed_ptr, | |
| scale_base_ptr, | |
| zp_base_ptr, | |
| best_step_ptr, | |
| best_error_ptr, | |
| num_rows, | |
| num_groups, | |
| group_size, | |
| total_steps, | |
| stride_obs_row, | |
| stride_obs_group, | |
| stride_scale_row, | |
| inv_grid, | |
| q_min, | |
| q_max, | |
| norm, | |
| patience, | |
| error_buffer, | |
| scale_eps, | |
| BLOCK_G: tl.constexpr, | |
| TILE_GROUPS: tl.constexpr, | |
| BLOCK_TILE_GROUPS: tl.constexpr, | |
| TOTAL_STEPS: tl.constexpr, | |
| QUANT_TYPE: tl.constexpr, | |
| DEQUANT_DTYPE: tl.constexpr, | |
| OBSERVED_DTYPE: tl.constexpr, | |
| HAS_ZP: tl.constexpr, | |
| SCALE_ROUND_TYPE: tl.constexpr, | |
| ): | |
| """Buffered group-major MSE search tiled across adjacent qparam groups. | |
| Each lane has independent best-error and patience state. Unlike the | |
| scalar-group implementation, inactive lanes still participate in vector | |
| QDQ; Triton cannot predicate away just those lanes' arithmetic. This | |
| variant therefore isolates the throughput benefit of a larger tile from | |
| the work-elision benefit of per-group patience. | |
| """ | |
| pid = tl.program_id(0) | |
| tile_offsets = tl.arange(0, BLOCK_TILE_GROUPS) | |
| linear_groups = pid * TILE_GROUPS + tile_offsets | |
| row = linear_groups // num_groups | |
| group = linear_groups % num_groups | |
| group_mask = (tile_offsets < TILE_GROUPS) & (linear_groups < num_rows * num_groups) | |
| group_idx = tl.arange(0, BLOCK_G) | |
| value_mask = group_mask[:, None] & (group_idx[None, :] < group_size) | |
| obs = tl.load( | |
| observed_ptr | |
| + row[:, None] * stride_obs_row | |
| + group[:, None] * stride_obs_group | |
| + group_idx[None, :], | |
| mask=value_mask, | |
| other=0.0, | |
| ).to(tl.float32) | |
| qparam_offsets = row * stride_scale_row + group | |
| scale_base = tl.load(scale_base_ptr + qparam_offsets, mask=group_mask, other=1.0) | |
| zp = scale_base * 0.0 | |
| if HAS_ZP: | |
| zp = tl.load(zp_base_ptr + qparam_offsets, mask=group_mask, other=0.0).to( | |
| tl.float32 | |
| ) | |
| if OBSERVED_DTYPE == 1: | |
| best_err = tl.full((BLOCK_TILE_GROUPS,), float("inf"), tl.bfloat16) | |
| elif OBSERVED_DTYPE == 2: | |
| best_err = tl.full((BLOCK_TILE_GROUPS,), float("inf"), tl.float16) | |
| else: | |
| best_err = tl.full((BLOCK_TILE_GROUPS,), float("inf"), tl.float32) | |
| best_step = tl.zeros((BLOCK_TILE_GROUPS,), tl.int32) | |
| stale = tl.zeros((BLOCK_TILE_GROUPS,), tl.int32) | |
| for step in range(TOTAL_STEPS): | |
| if step < total_steps: | |
| active = stale < patience | |
| scale = _round_candidate_scale( | |
| scale_base.to(tl.float32) * (1.0 - step * inv_grid), | |
| SCALE_ROUND_TYPE=SCALE_ROUND_TYPE, | |
| QUANT_TYPE=QUANT_TYPE, | |
| ) | |
| scale = tl.maximum( | |
| tl.where(scale == 0.0, scale_eps, scale), 1.1754943508222875e-38 | |
| )[:, None] | |
| quantized = _gptq_quantize_dequantize( | |
| obs, | |
| scale, | |
| zp[:, None], | |
| q_min, | |
| q_max, | |
| QUANT_TYPE=QUANT_TYPE, | |
| DEQUANT_DTYPE=DEQUANT_DTYPE, | |
| HAS_ZP=HAS_ZP, | |
| ) | |
| if OBSERVED_DTYPE == 1: | |
| obs_for_error = obs.to(tl.bfloat16) | |
| quantized = quantized.to(tl.bfloat16) | |
| elif OBSERVED_DTYPE == 2: | |
| obs_for_error = obs.to(tl.float16) | |
| quantized = quantized.to(tl.float16) | |
| else: | |
| obs_for_error = obs | |
| diff = tl.abs(quantized - obs_for_error).to(tl.float32) | |
| diff_pow = tl.extra.cuda.libdevice.pow(diff, norm.to(tl.float32)) | |
| if OBSERVED_DTYPE == 1: | |
| diff_pow = diff_pow.to(tl.bfloat16) | |
| elif OBSERVED_DTYPE == 2: | |
| diff_pow = diff_pow.to(tl.float16) | |
| err = tl.sum(tl.where(value_mask, diff_pow, 0.0), axis=1).to(best_err.dtype) | |
| previous_best = best_err | |
| is_better = active & (err < previous_best) | |
| best_err = tl.where(is_better, err, best_err) | |
| best_step = tl.where(is_better, step, best_step) | |
| within_buffer = err <= previous_best * (1.0 + error_buffer) | |
| stale = tl.where( | |
| active & within_buffer, | |
| 0, | |
| tl.where(active, stale + 1, stale), | |
| ) | |
| tl.store(best_step_ptr + linear_groups, best_step, mask=group_mask) | |
| tl.store(best_error_ptr + linear_groups, best_err, mask=group_mask) | |
| @triton.jit | |
| def _base_scale_grid_search_gptq_quant_split_error_kernel( | |
| observed_ptr, | |
| scale_base_ptr, | |
| zp_base_ptr, | |
| partial_errors_ptr, | |
| num_rows, | |
| num_groups, | |
| group_size, | |
| stride_obs_row, | |
| stride_obs_group, | |
| stride_scale_row, | |
| inv_grid, | |
| q_min, | |
| q_max, | |
| norm, | |
| scale_eps, | |
| BLOCK_VALUES: tl.constexpr, | |
| TOTAL_STEPS: tl.constexpr, | |
| NUM_CHUNKS: tl.constexpr, | |
| QUANT_TYPE: tl.constexpr, | |
| DEQUANT_DTYPE: tl.constexpr, | |
| OBSERVED_DTYPE: tl.constexpr, | |
| HAS_ZP: tl.constexpr, | |
| SCALE_ROUND_TYPE: tl.constexpr, | |
| ): | |
| """Evaluate all candidate errors for one tile inside a large qparam group.""" | |
| pid = tl.program_id(0) | |
| qparam = pid // NUM_CHUNKS | |
| chunk = pid % NUM_CHUNKS | |
| total_qparams = num_rows * num_groups | |
| qparam_mask = qparam < total_qparams | |
| row = qparam // num_groups | |
| group = qparam % num_groups | |
| offsets = chunk * BLOCK_VALUES + tl.arange(0, BLOCK_VALUES) | |
| value_mask = qparam_mask & (offsets < group_size) | |
| obs = tl.load( | |
| observed_ptr + row * stride_obs_row + group * stride_obs_group + offsets, | |
| mask=value_mask, | |
| other=0.0, | |
| ).to(tl.float32) | |
| qparam_offset = row * stride_scale_row + group | |
| scale_base = tl.load(scale_base_ptr + qparam_offset, mask=qparam_mask, other=1.0) | |
| zp = scale_base * 0.0 | |
| if HAS_ZP: | |
| zp = tl.load(zp_base_ptr + qparam_offset, mask=qparam_mask, other=0.0).to( | |
| tl.float32 | |
| ) | |
| for step in tl.static_range(0, TOTAL_STEPS): | |
| scale = _round_candidate_scale( | |
| scale_base.to(tl.float32) * (1.0 - step * inv_grid), | |
| SCALE_ROUND_TYPE=SCALE_ROUND_TYPE, | |
| QUANT_TYPE=QUANT_TYPE, | |
| ) | |
| scale = tl.maximum( | |
| tl.where(scale == 0.0, scale_eps, scale), 1.1754943508222875e-38 | |
| ) | |
| quantized = _gptq_quantize_dequantize( | |
| obs, | |
| scale, | |
| zp, | |
| q_min, | |
| q_max, | |
| QUANT_TYPE=QUANT_TYPE, | |
| DEQUANT_DTYPE=DEQUANT_DTYPE, | |
| HAS_ZP=HAS_ZP, | |
| ) | |
| if OBSERVED_DTYPE == 1: | |
| obs_for_error = obs.to(tl.bfloat16) | |
| quantized = quantized.to(tl.bfloat16) | |
| elif OBSERVED_DTYPE == 2: | |
| obs_for_error = obs.to(tl.float16) | |
| quantized = quantized.to(tl.float16) | |
| else: | |
| obs_for_error = obs | |
| diff = tl.abs(quantized - obs_for_error).to(tl.float32) | |
| diff_pow = tl.extra.cuda.libdevice.pow(diff, norm.to(tl.float32)) | |
| if OBSERVED_DTYPE == 1: | |
| diff_pow = diff_pow.to(tl.bfloat16) | |
| elif OBSERVED_DTYPE == 2: | |
| diff_pow = diff_pow.to(tl.float16) | |
| error = tl.sum(tl.where(value_mask, diff_pow, 0.0)) | |
| tl.store( | |
| partial_errors_ptr + (step * total_qparams + qparam) * NUM_CHUNKS + chunk, | |
| error, | |
| mask=qparam_mask, | |
| ) | |
| @triton.jit | |
| def _base_scale_grid_search_gptq_quant_reduce_split_errors_kernel( | |
| partial_errors_ptr, | |
| best_step_ptr, | |
| best_error_ptr, | |
| total_qparams, | |
| patience, | |
| error_buffer, | |
| BLOCK_CHUNKS: tl.constexpr, | |
| TOTAL_STEPS: tl.constexpr, | |
| NUM_CHUNKS: tl.constexpr, | |
| ): | |
| """Reduce value-tile errors, then apply buffered per-qparam patience.""" | |
| qparam = tl.program_id(0) | |
| qparam_mask = qparam < total_qparams | |
| chunk_offsets = tl.arange(0, BLOCK_CHUNKS) | |
| chunk_mask = chunk_offsets < NUM_CHUNKS | |
| best_error = tl.full([], float("inf"), tl.float32) | |
| best_step = 0 | |
| stale = 0 | |
| for step in tl.static_range(0, TOTAL_STEPS): | |
| partials = tl.load( | |
| partial_errors_ptr | |
| + (step * total_qparams + qparam) * NUM_CHUNKS | |
| + chunk_offsets, | |
| mask=qparam_mask & chunk_mask, | |
| other=0.0, | |
| ) | |
| error = tl.sum(partials) | |
| active = stale < patience | |
| previous_best = best_error | |
| is_better = active & (error < previous_best) | |
| best_error = tl.where(is_better, error, best_error) | |
| best_step = tl.where(is_better, step, best_step) | |
| within_buffer = error <= previous_best * (1.0 + error_buffer) | |
| stale = tl.where(active & within_buffer, 0, tl.where(active, stale + 1, stale)) | |
| tl.store(best_step_ptr + qparam, best_step, mask=qparam_mask) | |
| tl.store(best_error_ptr + qparam, best_error, mask=qparam_mask) | |
| @triton.jit | |
| def _vectorized_grid_search_gptq_quant_kernel( | |
| observed_ptr, | |
| all_scales_ptr, | |
| all_zps_ptr, | |
| best_step_ptr, | |
| best_error_ptr, | |
| num_rows, | |
| num_groups, | |
| group_size, | |
| total_steps, | |
| stride_obs_row, | |
| stride_obs_group, | |
| stride_scale_step, | |
| stride_scale_row, | |
| q_min, | |
| q_max, | |
| norm, | |
| patience, | |
| BLOCK_G: tl.constexpr, | |
| TOTAL_STEPS: tl.constexpr, | |
| QUANT_TYPE: tl.constexpr, | |
| DEQUANT_DTYPE: tl.constexpr, | |
| OBSERVED_DTYPE: tl.constexpr, | |
| HAS_ZP: tl.constexpr, | |
| USE_PATIENCE: tl.constexpr, | |
| ): | |
| """Precomputed-qparam GPTQ family: vectorize grid steps with group values. | |
| Unlike the serial GPTQ sibling, this evaluates one | |
| ``[TOTAL_STEPS, BLOCK_G]`` tile and reduces over all steps afterward. It | |
| exposes more parallelism but cannot save quantization work via patience. | |
| """ | |
| pid = tl.program_id(0) | |
| row = pid // num_groups | |
| group = pid % num_groups | |
| if row >= num_rows: | |
| return | |
| step_idx = tl.arange(0, TOTAL_STEPS) | |
| group_idx = tl.arange(0, BLOCK_G) | |
| step_offsets = step_idx[:, None] | |
| group_offsets = group_idx[None, :] | |
| step_mask = step_offsets < total_steps | |
| group_mask = group_offsets < group_size | |
| value_mask = step_mask & group_mask | |
| obs = tl.load( | |
| observed_ptr + row * stride_obs_row + group * stride_obs_group + group_offsets, | |
| mask=group_mask, | |
| other=0.0, | |
| ).to(tl.float32) | |
| scale = tl.load( | |
| all_scales_ptr | |
| + step_offsets * stride_scale_step | |
| + row * stride_scale_row | |
| + group, | |
| mask=step_mask, | |
| other=1.0, | |
| ).to(tl.float32) | |
| scale = tl.maximum(scale, 1.1754943508222875e-38) | |
| zp = scale * 0.0 | |
| if HAS_ZP: | |
| zp = tl.load( | |
| all_zps_ptr | |
| + step_offsets * stride_scale_step | |
| + row * stride_scale_row | |
| + group, | |
| mask=step_mask, | |
| other=0.0, | |
| ).to(tl.float32) | |
| quantized = _gptq_quantize_dequantize( | |
| obs, | |
| scale, | |
| zp, | |
| q_min, | |
| q_max, | |
| QUANT_TYPE=QUANT_TYPE, | |
| DEQUANT_DTYPE=DEQUANT_DTYPE, | |
| HAS_ZP=HAS_ZP, | |
| ) | |
| if OBSERVED_DTYPE == 1: | |
| obs_for_error = obs.to(tl.bfloat16) | |
| quantized = quantized.to(tl.bfloat16) | |
| elif OBSERVED_DTYPE == 2: | |
| obs_for_error = obs.to(tl.float16) | |
| quantized = quantized.to(tl.float16) | |
| else: | |
| obs_for_error = obs | |
| diff = tl.abs(quantized - obs_for_error).to(tl.float32) | |
| diff_pow = tl.extra.cuda.libdevice.pow(diff, norm.to(tl.float32)) | |
| if OBSERVED_DTYPE == 1: | |
| diff_pow = diff_pow.to(tl.bfloat16) | |
| elif OBSERVED_DTYPE == 2: | |
| diff_pow = diff_pow.to(tl.float16) | |
| errors = tl.sum(tl.where(value_mask, diff_pow, 0.0), axis=1) | |
| if OBSERVED_DTYPE == 1: | |
| errors = errors.to(tl.bfloat16) | |
| elif OBSERVED_DTYPE == 2: | |
| errors = errors.to(tl.float16) | |
| errors = tl.where(step_idx < total_steps, errors, float("inf")) | |
| best_err, best_s = tl.min( | |
| errors, | |
| axis=0, | |
| return_indices=True, | |
| return_indices_tie_break_left=True, | |
| ) | |
| tl.store(best_step_ptr + row * num_groups + group, best_s) | |
| tl.store(best_error_ptr + row * num_groups + group, best_err) | |
| def _gptq_quant_type(args): | |
| """Return the quantization mode constants used by the GPTQ Triton kernel.""" | |
| if args.type == QuantizationType.INT: | |
| return 0 | |
| if args.type == QuantizationType.FLOAT and args.num_bits == 4: | |
| return 1 | |
| if args.type == QuantizationType.FLOAT and args.num_bits == 8: | |
| return 2 | |
| raise ValueError( | |
| "GPTQ-style MSE quantization supports INT, FP4 E2M1, and FP8 E4M3; " | |
| f"got type={args.type}, num_bits={args.num_bits}" | |
| ) | |
| def _dequant_dtype_code(dtype): | |
| """Return the GPTQ kernel's dequantization dtype code.""" | |
| if dtype == torch.float32: | |
| return 0 | |
| if dtype == torch.bfloat16: | |
| return 1 | |
| if dtype == torch.float16: | |
| return 2 | |
| raise ValueError( | |
| "GPTQ-style MSE quantization supports float32, bfloat16, or float16 " | |
| f"scale dtypes, got {dtype}" | |
| ) | |
| def _observed_dtype_code(dtype): | |
| """Return the observed dtype code used for MSE error evaluation.""" | |
| if dtype == torch.float32: | |
| return 0 | |
| if dtype == torch.bfloat16: | |
| return 1 | |
| if dtype == torch.float16: | |
| return 2 | |
| raise ValueError( | |
| "GPTQ-style MSE quantization supports float32, bfloat16, or float16 " | |
| f"observed dtypes, got {dtype}" | |
| ) | |
| def _launch_gptq_quant_grid_search( | |
| kernel, | |
| observed, | |
| args, | |
| token_args, | |
| maxshrink, | |
| patience, | |
| grid, | |
| norm, | |
| chunk_size, | |
| use_patience=False, | |
| ): | |
| """Shared launcher for GPTQ-style grid-search kernels.""" | |
| del token_args, chunk_size | |
| min_val = torch.amin(observed, dim=(0, -1)) | |
| max_val = torch.amax(observed, dim=(0, -1)) | |
| total_steps = int(maxshrink * grid) | |
| all_scales = [] | |
| all_zps = [] | |
| for i in range(total_steps): | |
| p = 1 - i / grid | |
| s, zp = calculate_qparams( | |
| min_vals=min_val * p, | |
| max_vals=max_val * p, | |
| quantization_args=args, | |
| global_scale=None, | |
| ) | |
| all_scales.append(s) | |
| all_zps.append(zp) | |
| all_scales = torch.stack(all_scales).contiguous() | |
| # The PR's GPTQ kernel widens zero points before entering Triton. This | |
| # also handles FP8 zero-point tensors used by floating-point formats. | |
| all_zps = torch.stack(all_zps).to(dtype=torch.float32).contiguous() | |
| _, num_rows, num_groups, group_size = observed.shape | |
| best_step = torch.zeros( | |
| num_rows, num_groups, dtype=torch.int32, device=observed.device | |
| ) | |
| best_error = torch.full( | |
| (num_rows, num_groups), | |
| float("inf"), | |
| device=observed.device, | |
| dtype=torch.float32, | |
| ) | |
| observed_contig = observed.contiguous() | |
| BLOCK_G = triton.next_power_of_2(group_size) | |
| TOTAL_STEPS = triton.next_power_of_2(total_steps) | |
| grid_launch = (num_rows * num_groups,) | |
| q_min, q_max = calculate_range(args, observed.device) | |
| quant_type = _gptq_quant_type(args) | |
| dequant_dtype = _dequant_dtype_code(all_scales.dtype) | |
| observed_dtype = _observed_dtype_code(observed.dtype) | |
| has_zp = not args.symmetric | |
| # A dummy zero-point allocation keeps the pointer valid when symmetric | |
| # quantization compiles out the zero-point path. | |
| zp_ptr = all_zps if has_zp else all_scales | |
| kernel[grid_launch]( | |
| observed_contig, | |
| all_scales, | |
| zp_ptr, | |
| best_step, | |
| best_error, | |
| num_rows, | |
| num_groups, | |
| group_size, | |
| total_steps, | |
| observed_contig.stride(1), | |
| observed_contig.stride(2), | |
| all_scales.stride(0), | |
| all_scales.stride(1), | |
| float(q_min), | |
| float(q_max), | |
| norm, | |
| patience, | |
| BLOCK_G=BLOCK_G, | |
| TOTAL_STEPS=TOTAL_STEPS, | |
| QUANT_TYPE=quant_type, | |
| DEQUANT_DTYPE=dequant_dtype, | |
| OBSERVED_DTYPE=observed_dtype, | |
| HAS_ZP=has_zp, | |
| USE_PATIENCE=use_patience, | |
| ) | |
| ps = torch.tensor( | |
| [1.0 - i / grid for i in range(total_steps)], | |
| dtype=min_val.dtype, | |
| device=min_val.device, | |
| ) | |
| best_p = ps[best_step.long()] | |
| return min_val * best_p, max_val * best_p | |
| def grid_search_triton_gptq_quant( | |
| observed, args, token_args, maxshrink, patience, grid, norm, chunk_size | |
| ): | |
| """Serial-step grid search with GPTQ quantize/dequantize arithmetic.""" | |
| return _launch_gptq_quant_grid_search( | |
| _fused_grid_search_gptq_quant_kernel, | |
| observed, | |
| args, | |
| token_args, | |
| maxshrink, | |
| patience, | |
| grid, | |
| norm, | |
| chunk_size, | |
| ) | |
| def grid_search_triton_gptq_quant_vec( | |
| observed, args, token_args, maxshrink, patience, grid, norm, chunk_size | |
| ): | |
| """Fully vectorized-step grid search with GPTQ quantization arithmetic.""" | |
| return _launch_gptq_quant_grid_search( | |
| _vectorized_grid_search_gptq_quant_kernel, | |
| observed, | |
| args, | |
| token_args, | |
| maxshrink, | |
| patience, | |
| grid, | |
| norm, | |
| chunk_size, | |
| ) | |
| def grid_search_triton_gptq_quant_p( | |
| observed, args, token_args, maxshrink, patience, grid, norm, chunk_size | |
| ): | |
| """Exact GPTQ-style grid search with per-group patience stopping.""" | |
| return _launch_gptq_quant_grid_search( | |
| _fused_grid_search_gptq_quant_kernel, | |
| observed, | |
| args, | |
| token_args, | |
| maxshrink, | |
| patience, | |
| grid, | |
| norm, | |
| chunk_size, | |
| use_patience=True, | |
| ) | |
| @triton.jit | |
| def _hierarchical_gptq_quant_kernel( | |
| observed_ptr, | |
| scale_base_ptr, | |
| best_p_ptr, | |
| num_rows, | |
| num_groups, | |
| group_size, | |
| stride_obs_row, | |
| stride_obs_group, | |
| stride_scale_row, | |
| q_min, | |
| q_max, | |
| norm, | |
| p_min: tl.constexpr, | |
| p_max: tl.constexpr, | |
| N: tl.constexpr, | |
| KEEP_NUM: tl.constexpr, | |
| KEEP_DEN: tl.constexpr, | |
| M: tl.constexpr, | |
| K: tl.constexpr, | |
| MAX_KEEP: tl.constexpr, | |
| BLOCK_G: tl.constexpr, | |
| BLOCK_C: tl.constexpr, | |
| QUANT_TYPE: tl.constexpr, | |
| DEQUANT_DTYPE: tl.constexpr, | |
| USE_L2: tl.constexpr, | |
| USE_FAST_POW: tl.constexpr, | |
| ): | |
| """Hierarchical GPTQ family: configurable coarse-to-fine search per group. | |
| It evaluates N coarse candidates, retains a fraction, and repeatedly | |
| refines survivors inside one program. Unlike exhaustive/patience families, | |
| it reduces candidate count geometrically but introduces sequential | |
| generations and survivor-selection bookkeeping. | |
| """ | |
| pid = tl.program_id(0) | |
| row = pid // num_groups | |
| group = pid % num_groups | |
| if row >= num_rows: | |
| return | |
| g_idx = tl.arange(0, BLOCK_G) | |
| g_mask = g_idx < group_size | |
| obs = tl.load( | |
| observed_ptr + row * stride_obs_row + group * stride_obs_group + g_idx, | |
| mask=g_mask, | |
| other=0.0, | |
| ).to(tl.float32) | |
| scale_base = tl.load(scale_base_ptr + row * stride_scale_row + group).to(tl.float32) | |
| lanes = tl.arange(0, BLOCK_C) | |
| candidates = p_min + lanes * ((p_max - p_min) / (N - 1)) | |
| active_count = N | |
| spacing = (p_max - p_min) / (N - 1) | |
| best_error = float("inf") | |
| best_p = p_max | |
| norm_f = norm.to(tl.float32) | |
| # K is the number of refinement generations after the initial N points. | |
| for level in range(K + 1): | |
| candidate_mask = lanes < active_count | |
| scales = tl.maximum(scale_base * candidates[:, None], 1.1754943508222875e-38) | |
| quantized = _gptq_quantize_dequantize( | |
| obs[None, :], | |
| scales, | |
| scales * 0.0, | |
| q_min, | |
| q_max, | |
| QUANT_TYPE=QUANT_TYPE, | |
| DEQUANT_DTYPE=DEQUANT_DTYPE, | |
| HAS_ZP=False, | |
| ) | |
| diff = tl.abs(quantized - obs[None, :]).to(tl.float32) | |
| if USE_L2: | |
| diff_pow = diff * diff | |
| elif USE_FAST_POW: | |
| diff_pow = diff * diff * tl.exp2((norm_f - 2.0) * tl.log2(diff)) | |
| else: | |
| diff_pow = tl.extra.cuda.libdevice.pow(diff, norm_f) | |
| errors = tl.sum(tl.where(g_mask[None, :], diff_pow, 0.0), axis=1) | |
| errors = tl.where(candidate_mask, errors, float("inf")) | |
| stage_error = tl.min(errors, axis=0) | |
| stage_index = tl.argmin(errors, axis=0) | |
| stage_p = tl.sum(tl.where(lanes == stage_index, candidates, 0.0), axis=0) | |
| improved = stage_error < best_error | |
| best_error = tl.where(improved, stage_error, best_error) | |
| best_p = tl.where(improved, stage_p, best_p) | |
| if level < K: | |
| keep_count = active_count * KEEP_NUM // KEEP_DEN | |
| retained = tl.zeros((BLOCK_C,), tl.float32) | |
| remaining_errors = errors | |
| for rank in range(MAX_KEEP): | |
| if rank < keep_count: | |
| selected_index = tl.argmin(remaining_errors, axis=0) | |
| selected_p = tl.sum( | |
| tl.where(lanes == selected_index, candidates, 0.0), axis=0 | |
| ) | |
| retained = tl.where(lanes == rank, selected_p, retained) | |
| remaining_errors = tl.where( | |
| lanes == selected_index, float("inf"), remaining_errors | |
| ) | |
| parent_index = lanes // M | |
| child_index = lanes % M | |
| parent = tl.sum( | |
| tl.where( | |
| lanes[None, :] == parent_index[:, None], | |
| retained[None, :], | |
| 0.0, | |
| ), | |
| axis=1, | |
| ) | |
| offset = ((2 * child_index + 1 - M) * spacing) / (2 * M) | |
| candidates = tl.maximum(p_min, tl.minimum(p_max, parent + offset)) | |
| active_count = keep_count * M | |
| spacing = spacing / (2 * M) | |
| tl.store(best_p_ptr + row * num_groups + group, best_p) | |
| def grid_search_triton_hierarchical( | |
| observed, args, token_args, maxshrink, patience, grid, norm, chunk_size | |
| ): | |
| """Configurable fused hierarchical search using GPTQ QDQ arithmetic.""" | |
| del token_args, maxshrink, patience, grid, chunk_size | |
| config = HIER_CONFIG | |
| n = config["n"] | |
| m = config["m"] | |
| keep_num = config["keep_num"] | |
| keep_den = config["keep_den"] | |
| k = config["k"] | |
| active_count = n | |
| max_candidates = n | |
| max_keep = 0 | |
| for _ in range(k): | |
| keep_count = active_count * keep_num // keep_den | |
| if keep_count < 1 or active_count * keep_num % keep_den: | |
| raise ValueError( | |
| "Each hierarchical generation must retain a positive integer " | |
| "number of candidates" | |
| ) | |
| max_keep = max(max_keep, keep_count) | |
| active_count = keep_count * m | |
| max_candidates = max(max_candidates, active_count) | |
| if m <= 0 or m % 2: | |
| raise ValueError("hier-m must be a positive even integer") | |
| min_val = torch.amin(observed, dim=(0, -1)) | |
| max_val = torch.amax(observed, dim=(0, -1)) | |
| scale_base, _ = calculate_qparams( | |
| min_vals=min_val, | |
| max_vals=max_val, | |
| quantization_args=args, | |
| global_scale=None, | |
| ) | |
| _, num_rows, num_groups, group_size = observed.shape | |
| best_p = torch.empty( | |
| (num_rows, num_groups), dtype=torch.float32, device=observed.device | |
| ) | |
| q_min, q_max = calculate_range(args, observed.device) | |
| block_g = triton.next_power_of_2(group_size) | |
| block_c = triton.next_power_of_2(max_candidates) | |
| observed_contig = observed.contiguous() | |
| _hierarchical_gptq_quant_kernel[(num_rows * num_groups,)]( | |
| observed_contig, | |
| scale_base.contiguous(), | |
| best_p, | |
| num_rows, | |
| num_groups, | |
| group_size, | |
| observed_contig.stride(1), | |
| observed_contig.stride(2), | |
| scale_base.stride(0), | |
| float(q_min), | |
| float(q_max), | |
| norm, | |
| p_min=config["p_min"], | |
| p_max=config["p_max"], | |
| N=n, | |
| KEEP_NUM=keep_num, | |
| KEEP_DEN=keep_den, | |
| M=m, | |
| K=k, | |
| MAX_KEEP=max_keep, | |
| BLOCK_G=block_g, | |
| BLOCK_C=block_c, | |
| QUANT_TYPE=_gptq_quant_type(args), | |
| DEQUANT_DTYPE=_dequant_dtype_code(scale_base.dtype), | |
| USE_L2=norm == 2.0, | |
| USE_FAST_POW=config["fast_pow"], | |
| ) | |
| return min_val * best_p, max_val * best_p | |
| def grid_search_triton_hierarchical_fast( | |
| observed, args, token_args, maxshrink, patience, grid, norm, chunk_size | |
| ): | |
| """Hierarchical search that always uses the fast approximate power.""" | |
| previous = HIER_CONFIG["fast_pow"] | |
| HIER_CONFIG["fast_pow"] = True | |
| try: | |
| return grid_search_triton_hierarchical( | |
| observed, | |
| args, | |
| token_args, | |
| maxshrink, | |
| patience, | |
| grid, | |
| norm, | |
| chunk_size, | |
| ) | |
| finally: | |
| HIER_CONFIG["fast_pow"] = previous | |
| @triton.jit | |
| def _insert_top4(error, candidate, e0, p0, e1, p1, e2, p2, e3, p3): | |
| """Hierarchical-family inline utility inserting one scalar into a top-4.""" | |
| swap = error < e0 | |
| error, e0 = tl.where(swap, e0, error), tl.where(swap, error, e0) | |
| candidate, p0 = ( | |
| tl.where(swap, p0, candidate), | |
| tl.where(swap, candidate, p0), | |
| ) | |
| swap = error < e1 | |
| error, e1 = tl.where(swap, e1, error), tl.where(swap, error, e1) | |
| candidate, p1 = ( | |
| tl.where(swap, p1, candidate), | |
| tl.where(swap, candidate, p1), | |
| ) | |
| swap = error < e2 | |
| error, e2 = tl.where(swap, e2, error), tl.where(swap, error, e2) | |
| candidate, p2 = ( | |
| tl.where(swap, p2, candidate), | |
| tl.where(swap, candidate, p2), | |
| ) | |
| swap = error < e3 | |
| e3 = tl.where(swap, error, e3) | |
| p3 = tl.where(swap, candidate, p3) | |
| return e0, p0, e1, p1, e2, p2, e3, p3 | |
| @triton.jit | |
| def _hierarchical_candidate_error( | |
| obs, | |
| g_mask, | |
| scale, | |
| q_min, | |
| q_max, | |
| norm_f, | |
| QUANT_TYPE: tl.constexpr, | |
| DEQUANT_DTYPE: tl.constexpr, | |
| USE_L2: tl.constexpr, | |
| ): | |
| """Serial-hierarchical inline utility evaluating one candidate's error.""" | |
| quantized = _gptq_quantize_dequantize( | |
| obs, | |
| scale, | |
| scale * 0.0, | |
| q_min, | |
| q_max, | |
| QUANT_TYPE=QUANT_TYPE, | |
| DEQUANT_DTYPE=DEQUANT_DTYPE, | |
| HAS_ZP=False, | |
| ) | |
| diff = tl.abs(quantized - obs).to(tl.float32) | |
| if USE_L2: | |
| diff_pow = diff * diff | |
| else: | |
| diff_pow = tl.extra.cuda.libdevice.pow(diff, norm_f) | |
| return tl.sum(tl.where(g_mask, diff_pow, 0.0), axis=0) | |
| @triton.jit | |
| def _hierarchical_serial_32_4_8_kernel( | |
| observed_ptr, | |
| scale_base_ptr, | |
| best_p_ptr, | |
| num_rows, | |
| num_groups, | |
| group_size, | |
| stride_obs_row, | |
| stride_obs_group, | |
| stride_scale_row, | |
| q_min, | |
| q_max, | |
| norm, | |
| p_min: tl.constexpr, | |
| p_max: tl.constexpr, | |
| BLOCK_G: tl.constexpr, | |
| QUANT_TYPE: tl.constexpr, | |
| DEQUANT_DTYPE: tl.constexpr, | |
| USE_L2: tl.constexpr, | |
| ): | |
| """Hierarchical GPTQ family: scalar specialized 32 -> top-4 -> 32 search. | |
| This hard-codes the favored configuration and evaluates candidates serially | |
| to minimize tile pressure. It differs from the generic kernel by removing | |
| configurability and from the tiled sibling by avoiding candidate vectors. | |
| """ | |
| pid = tl.program_id(0) | |
| row = pid // num_groups | |
| group = pid % num_groups | |
| if row >= num_rows: | |
| return | |
| g_idx = tl.arange(0, BLOCK_G) | |
| g_mask = g_idx < group_size | |
| obs = tl.load( | |
| observed_ptr + row * stride_obs_row + group * stride_obs_group + g_idx, | |
| mask=g_mask, | |
| other=0.0, | |
| ).to(tl.float32) | |
| scale_base = tl.load(scale_base_ptr + row * stride_scale_row + group).to(tl.float32) | |
| norm_f = norm.to(tl.float32) | |
| e0 = float("inf") | |
| e1 = float("inf") | |
| e2 = float("inf") | |
| e3 = float("inf") | |
| p0 = p_min | |
| p1 = p_min | |
| p2 = p_min | |
| p3 = p_min | |
| spacing: tl.constexpr = (p_max - p_min) / 31.0 | |
| for coarse in range(32): | |
| candidate = p_min + coarse * spacing | |
| scale = tl.maximum(scale_base * candidate, 1.1754943508222875e-38) | |
| error = _hierarchical_candidate_error( | |
| obs, | |
| g_mask, | |
| scale, | |
| q_min, | |
| q_max, | |
| norm_f, | |
| QUANT_TYPE=QUANT_TYPE, | |
| DEQUANT_DTYPE=DEQUANT_DTYPE, | |
| USE_L2=USE_L2, | |
| ) | |
| e0, p0, e1, p1, e2, p2, e3, p3 = _insert_top4( | |
| error, candidate, e0, p0, e1, p1, e2, p2, e3, p3 | |
| ) | |
| best_error = e0 | |
| best_p = p0 | |
| for parent_index in range(4): | |
| if parent_index == 0: | |
| parent = p0 | |
| elif parent_index == 1: | |
| parent = p1 | |
| elif parent_index == 2: | |
| parent = p2 | |
| else: | |
| parent = p3 | |
| for child in range(8): | |
| offset = (2 * child + 1 - 8) * spacing / 16.0 | |
| candidate = tl.maximum(p_min, tl.minimum(p_max, parent + offset)) | |
| scale = tl.maximum(scale_base * candidate, 1.1754943508222875e-38) | |
| error = _hierarchical_candidate_error( | |
| obs, | |
| g_mask, | |
| scale, | |
| q_min, | |
| q_max, | |
| norm_f, | |
| QUANT_TYPE=QUANT_TYPE, | |
| DEQUANT_DTYPE=DEQUANT_DTYPE, | |
| USE_L2=USE_L2, | |
| ) | |
| improved = error < best_error | |
| best_error = tl.where(improved, error, best_error) | |
| best_p = tl.where(improved, candidate, best_p) | |
| tl.store(best_p_ptr + row * num_groups + group, best_p) | |
| def grid_search_triton_hierarchical_serial( | |
| observed, args, token_args, maxshrink, patience, grid, norm, chunk_size | |
| ): | |
| """Register-efficient specialized hierarchical NVFP4 search.""" | |
| del token_args, maxshrink, patience, grid, chunk_size | |
| if not args.symmetric: | |
| raise ValueError("The specialized hierarchical kernel requires symmetry") | |
| min_val = torch.amin(observed, dim=(0, -1)) | |
| max_val = torch.amax(observed, dim=(0, -1)) | |
| scale_base, _ = calculate_qparams( | |
| min_vals=min_val, | |
| max_vals=max_val, | |
| quantization_args=args, | |
| global_scale=None, | |
| ) | |
| _, num_rows, num_groups, group_size = observed.shape | |
| best_p = torch.empty( | |
| (num_rows, num_groups), dtype=torch.float32, device=observed.device | |
| ) | |
| observed_contig = observed.contiguous() | |
| scale_base = scale_base.contiguous() | |
| q_min, q_max = calculate_range(args, observed.device) | |
| _hierarchical_serial_32_4_8_kernel[(num_rows * num_groups,)]( | |
| observed_contig, | |
| scale_base, | |
| best_p, | |
| num_rows, | |
| num_groups, | |
| group_size, | |
| observed_contig.stride(1), | |
| observed_contig.stride(2), | |
| scale_base.stride(0), | |
| float(q_min), | |
| float(q_max), | |
| norm, | |
| p_min=HIER_CONFIG["p_min"], | |
| p_max=HIER_CONFIG["p_max"], | |
| BLOCK_G=triton.next_power_of_2(group_size), | |
| QUANT_TYPE=_gptq_quant_type(args), | |
| DEQUANT_DTYPE=_dequant_dtype_code(scale_base.dtype), | |
| USE_L2=norm == 2.0, | |
| ) | |
| return min_val * best_p, max_val * best_p | |
| @triton.jit | |
| def _hierarchical_tile_errors( | |
| obs, | |
| g_mask, | |
| scales, | |
| q_min, | |
| q_max, | |
| norm_f, | |
| QUANT_TYPE: tl.constexpr, | |
| DEQUANT_DTYPE: tl.constexpr, | |
| USE_L2: tl.constexpr, | |
| ): | |
| """Tiled-hierarchical inline utility evaluating a candidate vector.""" | |
| quantized = _gptq_quantize_dequantize( | |
| obs[None, :], | |
| scales[:, None], | |
| scales[:, None] * 0.0, | |
| q_min, | |
| q_max, | |
| QUANT_TYPE=QUANT_TYPE, | |
| DEQUANT_DTYPE=DEQUANT_DTYPE, | |
| HAS_ZP=False, | |
| ) | |
| diff = tl.abs(quantized - obs[None, :]).to(tl.float32) | |
| if USE_L2: | |
| diff_pow = diff * diff | |
| else: | |
| diff_pow = tl.extra.cuda.libdevice.pow(diff, norm_f) | |
| return tl.sum(tl.where(g_mask[None, :], diff_pow, 0.0), axis=1) | |
| @triton.jit | |
| def _hierarchical_tiled_32_4_8_kernel( | |
| observed_ptr, | |
| scale_base_ptr, | |
| best_p_ptr, | |
| num_rows, | |
| num_groups, | |
| group_size, | |
| stride_obs_row, | |
| stride_obs_group, | |
| stride_scale_row, | |
| q_min, | |
| q_max, | |
| norm, | |
| p_min: tl.constexpr, | |
| p_max: tl.constexpr, | |
| BLOCK_G: tl.constexpr, | |
| QUANT_TYPE: tl.constexpr, | |
| DEQUANT_DTYPE: tl.constexpr, | |
| USE_L2: tl.constexpr, | |
| ): | |
| """Hierarchical GPTQ family: tiled specialized 32 -> top-4 -> 32 search. | |
| This evaluates candidates in parallel tiles of eight, then performs scalar | |
| top-4 selection. It trades higher register pressure for more candidate | |
| parallelism compared with the serial specialized sibling. | |
| """ | |
| pid = tl.program_id(0) | |
| row = pid // num_groups | |
| group = pid % num_groups | |
| if row >= num_rows: | |
| return | |
| g_idx = tl.arange(0, BLOCK_G) | |
| g_mask = g_idx < group_size | |
| obs = tl.load( | |
| observed_ptr + row * stride_obs_row + group * stride_obs_group + g_idx, | |
| mask=g_mask, | |
| other=0.0, | |
| ).to(tl.float32) | |
| scale_base = tl.load(scale_base_ptr + row * stride_scale_row + group).to(tl.float32) | |
| norm_f = norm.to(tl.float32) | |
| lanes = tl.arange(0, 8) | |
| spacing: tl.constexpr = (p_max - p_min) / 31.0 | |
| e0 = float("inf") | |
| e1 = float("inf") | |
| e2 = float("inf") | |
| e3 = float("inf") | |
| p0 = p_min | |
| p1 = p_min | |
| p2 = p_min | |
| p3 = p_min | |
| for tile in range(4): | |
| candidates = p_min + (tile * 8 + lanes) * spacing | |
| scales = tl.maximum(scale_base * candidates, 1.1754943508222875e-38) | |
| errors = _hierarchical_tile_errors( | |
| obs, | |
| g_mask, | |
| scales, | |
| q_min, | |
| q_max, | |
| norm_f, | |
| QUANT_TYPE=QUANT_TYPE, | |
| DEQUANT_DTYPE=DEQUANT_DTYPE, | |
| USE_L2=USE_L2, | |
| ) | |
| for lane in range(8): | |
| error = tl.sum(tl.where(lanes == lane, errors, 0.0), axis=0) | |
| candidate = p_min + (tile * 8 + lane) * spacing | |
| e0, p0, e1, p1, e2, p2, e3, p3 = _insert_top4( | |
| error, candidate, e0, p0, e1, p1, e2, p2, e3, p3 | |
| ) | |
| best_error = e0 | |
| best_p = p0 | |
| child_offsets = (2 * lanes + 1 - 8) * spacing / 16.0 | |
| for parent_index in range(4): | |
| if parent_index == 0: | |
| parent = p0 | |
| elif parent_index == 1: | |
| parent = p1 | |
| elif parent_index == 2: | |
| parent = p2 | |
| else: | |
| parent = p3 | |
| candidates = tl.maximum(p_min, tl.minimum(p_max, parent + child_offsets)) | |
| scales = tl.maximum(scale_base * candidates, 1.1754943508222875e-38) | |
| errors = _hierarchical_tile_errors( | |
| obs, | |
| g_mask, | |
| scales, | |
| q_min, | |
| q_max, | |
| norm_f, | |
| QUANT_TYPE=QUANT_TYPE, | |
| DEQUANT_DTYPE=DEQUANT_DTYPE, | |
| USE_L2=USE_L2, | |
| ) | |
| stage_error = tl.min(errors, axis=0) | |
| stage_index = tl.argmin(errors, axis=0) | |
| stage_p = tl.sum(tl.where(lanes == stage_index, candidates, 0.0), axis=0) | |
| improved = stage_error < best_error | |
| best_error = tl.where(improved, stage_error, best_error) | |
| best_p = tl.where(improved, stage_p, best_p) | |
| tl.store(best_p_ptr + row * num_groups + group, best_p) | |
| def grid_search_triton_hierarchical_tiled( | |
| observed, args, token_args, maxshrink, patience, grid, norm, chunk_size | |
| ): | |
| """Specialized 32 -> 32 hierarchical search with 8-candidate tiles.""" | |
| del token_args, maxshrink, patience, grid, chunk_size | |
| if not args.symmetric: | |
| raise ValueError("The tiled hierarchical kernel requires symmetry") | |
| min_val = torch.amin(observed, dim=(0, -1)) | |
| max_val = torch.amax(observed, dim=(0, -1)) | |
| scale_base, _ = calculate_qparams( | |
| min_vals=min_val, | |
| max_vals=max_val, | |
| quantization_args=args, | |
| global_scale=None, | |
| ) | |
| _, num_rows, num_groups, group_size = observed.shape | |
| best_p = torch.empty( | |
| (num_rows, num_groups), dtype=torch.float32, device=observed.device | |
| ) | |
| observed_contig = observed.contiguous() | |
| scale_base = scale_base.contiguous() | |
| q_min, q_max = calculate_range(args, observed.device) | |
| _hierarchical_tiled_32_4_8_kernel[(num_rows * num_groups,)]( | |
| observed_contig, | |
| scale_base, | |
| best_p, | |
| num_rows, | |
| num_groups, | |
| group_size, | |
| observed_contig.stride(1), | |
| observed_contig.stride(2), | |
| scale_base.stride(0), | |
| float(q_min), | |
| float(q_max), | |
| norm, | |
| p_min=HIER_CONFIG["p_min"], | |
| p_max=HIER_CONFIG["p_max"], | |
| BLOCK_G=triton.next_power_of_2(group_size), | |
| QUANT_TYPE=_gptq_quant_type(args), | |
| DEQUANT_DTYPE=_dequant_dtype_code(scale_base.dtype), | |
| USE_L2=norm == 2.0, | |
| ) | |
| return min_val * best_p, max_val * best_p | |
| def _scale_round_config(dtype): | |
| """Return kernel rounding mode and zero-scale replacement for scale dtype.""" | |
| if dtype == torch.float32: | |
| return 0, torch.finfo(dtype).eps | |
| if dtype == torch.float16: | |
| return 1, torch.finfo(dtype).eps | |
| if dtype == torch.bfloat16: | |
| return 2, torch.finfo(dtype).eps | |
| if dtype == torch.float8_e4m3fn: | |
| return 3, 0.125 | |
| if dtype == torch.uint8: | |
| return 4, 2.0**-127 | |
| raise ValueError(f"unsupported scale dtype for in-kernel rounding: {dtype}") | |
| def _launch_gptq_base_scale_patience( | |
| observed, | |
| args, | |
| token_args, | |
| maxshrink, | |
| patience, | |
| grid, | |
| norm, | |
| chunk_size, | |
| round_scale, | |
| use_patience=True, | |
| error_buffer=0.0, | |
| ): | |
| """Launch a search deriving candidate scales from one base scale.""" | |
| del token_args, chunk_size | |
| if round_scale: | |
| if args.scale_dtype is None: | |
| raise ValueError("scale-rounding variant requires args.scale_dtype") | |
| scale_round_type, scale_eps = _scale_round_config(args.scale_dtype) | |
| if scale_round_type == 4 and not ( | |
| args.symmetric | |
| and args.type == QuantizationType.FLOAT | |
| and args.num_bits in (4, 8) | |
| and args.group_size == 32 | |
| ): | |
| raise ValueError( | |
| "E8M0 scale generation requires symmetric FLOAT4/FLOAT8 " | |
| "quantization with group_size=32" | |
| ) | |
| qparam_args = args.model_copy(update={"scale_dtype": None}) | |
| else: | |
| if args.scale_dtype is not None: | |
| raise ValueError( | |
| "base-scale variant requires scale_dtype=None; use the " | |
| "scale-rounding variant instead" | |
| ) | |
| qparam_args = args | |
| scale_round_type = 0 | |
| scale_eps = None | |
| min_val = torch.amin(observed, dim=(0, -1)) | |
| max_val = torch.amax(observed, dim=(0, -1)) | |
| total_steps = int(maxshrink * grid) | |
| scale_base, zp_base = calculate_qparams( | |
| min_vals=min_val, | |
| max_vals=max_val, | |
| quantization_args=qparam_args, | |
| global_scale=None, | |
| ) | |
| if scale_round_type == 4: | |
| # MX scale generation starts from the candidate group maximum, not | |
| # from the ordinary max/qmax scale used by other formats. | |
| scale_base = torch.maximum(torch.abs(min_val), torch.abs(max_val)) | |
| scale_base = scale_base.contiguous() | |
| zp_base = zp_base.to(dtype=torch.float32).contiguous() | |
| if scale_eps is None: | |
| _, scale_eps = _scale_round_config(scale_base.dtype) | |
| _, num_rows, num_groups, group_size = observed.shape | |
| best_step = torch.zeros( | |
| num_rows, num_groups, dtype=torch.int32, device=observed.device | |
| ) | |
| best_error = torch.full( | |
| (num_rows, num_groups), | |
| float("inf"), | |
| device=observed.device, | |
| dtype=torch.float32, | |
| ) | |
| observed_contig = observed.contiguous() | |
| q_min, q_max = calculate_range(args, observed.device) | |
| quant_type = _gptq_quant_type(args) | |
| dequant_dtype = _dequant_dtype_code(scale_base.dtype) | |
| observed_dtype = _observed_dtype_code(observed.dtype) | |
| has_zp = not args.symmetric | |
| zp_ptr = zp_base if has_zp else scale_base | |
| BLOCK_G = triton.next_power_of_2(group_size) | |
| grid_launch = (num_rows * num_groups,) | |
| if not use_patience: | |
| _base_scale_grid_search_gptq_quant_exhaustive_kernel[grid_launch]( | |
| observed_contig, | |
| scale_base, | |
| zp_ptr, | |
| best_step, | |
| best_error, | |
| num_rows, | |
| num_groups, | |
| group_size, | |
| observed_contig.stride(1), | |
| observed_contig.stride(2), | |
| scale_base.stride(0), | |
| 1.0 / grid, | |
| float(q_min), | |
| float(q_max), | |
| norm, | |
| scale_eps, | |
| BLOCK_G=BLOCK_G, | |
| TOTAL_STEPS=total_steps, | |
| QUANT_TYPE=quant_type, | |
| DEQUANT_DTYPE=dequant_dtype, | |
| OBSERVED_DTYPE=observed_dtype, | |
| HAS_ZP=has_zp, | |
| ) | |
| else: | |
| TOTAL_STEPS = triton.next_power_of_2(total_steps) | |
| _base_scale_grid_search_gptq_quant_patience_kernel[grid_launch]( | |
| observed_contig, | |
| scale_base, | |
| zp_ptr, | |
| best_step, | |
| best_error, | |
| num_rows, | |
| num_groups, | |
| group_size, | |
| total_steps, | |
| observed_contig.stride(1), | |
| observed_contig.stride(2), | |
| scale_base.stride(0), | |
| 1.0 / grid, | |
| float(q_min), | |
| float(q_max), | |
| norm, | |
| patience, | |
| error_buffer, | |
| scale_eps, | |
| BLOCK_G=BLOCK_G, | |
| TOTAL_STEPS=TOTAL_STEPS, | |
| QUANT_TYPE=quant_type, | |
| DEQUANT_DTYPE=dequant_dtype, | |
| OBSERVED_DTYPE=observed_dtype, | |
| HAS_ZP=has_zp, | |
| SCALE_ROUND_TYPE=scale_round_type, | |
| USE_PATIENCE=True, | |
| ) | |
| ps = torch.tensor( | |
| [1.0 - i / grid for i in range(total_steps)], | |
| dtype=min_val.dtype, | |
| device=min_val.device, | |
| ) | |
| best_p = ps[best_step.long()] | |
| return min_val * best_p, max_val * best_p | |
| def grid_search_triton_gptq_base_p( | |
| observed, args, token_args, maxshrink, patience, grid, norm, chunk_size | |
| ): | |
| """GPTQ quantization with base-scale derivation and per-group patience.""" | |
| return _launch_gptq_base_scale_patience( | |
| observed, | |
| args, | |
| token_args, | |
| maxshrink, | |
| patience, | |
| grid, | |
| norm, | |
| chunk_size, | |
| round_scale=False, | |
| ) | |
| def grid_search_triton_gptq_base( | |
| observed, args, token_args, maxshrink, patience, grid, norm, chunk_size | |
| ): | |
| """Exhaustive GPTQ quantization deriving candidates from one base scale.""" | |
| return _launch_gptq_base_scale_patience( | |
| observed, | |
| args, | |
| token_args, | |
| maxshrink, | |
| patience, | |
| grid, | |
| norm, | |
| chunk_size, | |
| round_scale=False, | |
| use_patience=False, | |
| ) | |
| def _launch_gptq_base_grid_major( | |
| observed, | |
| args, | |
| token_args, | |
| maxshrink, | |
| patience, | |
| grid, | |
| norm, | |
| chunk_size, | |
| tile_values, | |
| ): | |
| """Run an exhaustive base-scale search one grid point at a time in Triton. | |
| Unlike ``triton_gptq_base``, which gives each group one long program that | |
| scans every candidate, this launches the whole tensor for each candidate. | |
| It deliberately mirrors eager's grid-major scheduling while retaining | |
| fused QDQ/error/update work. The benchmark versions are exhaustive: the | |
| global eager-patience check would require a device-to-host synchronization | |
| after every candidate and obscure the scheduling comparison. | |
| """ | |
| del token_args, patience, chunk_size | |
| if args.scale_dtype is not None: | |
| raise ValueError("grid-major base-scale variants require scale_dtype=None") | |
| min_val = torch.amin(observed, dim=(0, -1)) | |
| max_val = torch.amax(observed, dim=(0, -1)) | |
| total_steps = int(maxshrink * grid) | |
| scale_base, zp_base = calculate_qparams( | |
| min_vals=min_val, | |
| max_vals=max_val, | |
| quantization_args=args, | |
| global_scale=None, | |
| ) | |
| scale_base = scale_base.contiguous() | |
| zp_base = zp_base.to(dtype=torch.float32).contiguous() | |
| _, scale_eps = _scale_round_config(scale_base.dtype) | |
| _, num_rows, num_groups, group_size = observed.shape | |
| best_step = torch.zeros( | |
| num_rows, num_groups, dtype=torch.int32, device=observed.device | |
| ) | |
| best_error = torch.full( | |
| (num_rows, num_groups), | |
| float("inf"), | |
| dtype=torch.float32, | |
| device=observed.device, | |
| ) | |
| observed = observed.contiguous() | |
| block_g = triton.next_power_of_2(group_size) | |
| tile_groups = max(1, tile_values // block_g) | |
| grid_launch = (triton.cdiv(num_rows * num_groups, tile_groups),) | |
| q_min, q_max = calculate_range(args, observed.device) | |
| has_zp = not args.symmetric | |
| for step in range(total_steps): | |
| _grid_major_base_scale_gptq_quant_kernel[grid_launch]( | |
| observed, | |
| scale_base, | |
| zp_base if has_zp else scale_base, | |
| best_step, | |
| best_error, | |
| num_rows, | |
| num_groups, | |
| group_size, | |
| observed.stride(1), | |
| observed.stride(2), | |
| scale_base.stride(0), | |
| step, | |
| 1.0 / grid, | |
| float(q_min), | |
| float(q_max), | |
| norm, | |
| scale_eps, | |
| BLOCK_G=block_g, | |
| TILE_GROUPS=tile_groups, | |
| QUANT_TYPE=_gptq_quant_type(args), | |
| DEQUANT_DTYPE=_dequant_dtype_code(scale_base.dtype), | |
| OBSERVED_DTYPE=_observed_dtype_code(observed.dtype), | |
| HAS_ZP=has_zp, | |
| ) | |
| ps = torch.arange(total_steps, device=min_val.device, dtype=min_val.dtype) | |
| best_p = (1.0 - ps / grid)[best_step.long()] | |
| return min_val * best_p, max_val * best_p | |
| def grid_search_triton_gptq_grid_major( | |
| observed, args, token_args, maxshrink, patience, grid, norm, chunk_size | |
| ): | |
| """Grid-major exhaustive search, one qparam group per Triton program.""" | |
| return _launch_gptq_base_grid_major( | |
| observed, args, token_args, maxshrink, patience, grid, norm, chunk_size, 16 | |
| ) | |
| def grid_search_triton_gptq_grid_major_tile64( | |
| observed, args, token_args, maxshrink, patience, grid, norm, chunk_size | |
| ): | |
| """Grid-major exhaustive search tiled to 64 values per program.""" | |
| return _launch_gptq_base_grid_major( | |
| observed, args, token_args, maxshrink, patience, grid, norm, chunk_size, 64 | |
| ) | |
| def grid_search_triton_gptq_grid_major_tile128( | |
| observed, args, token_args, maxshrink, patience, grid, norm, chunk_size | |
| ): | |
| """Grid-major exhaustive search tiled to 128 values per program.""" | |
| return _launch_gptq_base_grid_major( | |
| observed, args, token_args, maxshrink, patience, grid, norm, chunk_size, 128 | |
| ) | |
| def grid_search_triton_gptq_grid_major_tile256( | |
| observed, args, token_args, maxshrink, patience, grid, norm, chunk_size | |
| ): | |
| """Grid-major exhaustive search tiled to 256 values per program.""" | |
| return _launch_gptq_base_grid_major( | |
| observed, args, token_args, maxshrink, patience, grid, norm, chunk_size, 256 | |
| ) | |
| def grid_search_triton_gptq_grid_major_tile512( | |
| observed, args, token_args, maxshrink, patience, grid, norm, chunk_size | |
| ): | |
| """Grid-major exhaustive search tiled to 512 values per program.""" | |
| return _launch_gptq_base_grid_major( | |
| observed, args, token_args, maxshrink, patience, grid, norm, chunk_size, 512 | |
| ) | |
| def grid_search_triton_gptq_grid_major_tile1024( | |
| observed, args, token_args, maxshrink, patience, grid, norm, chunk_size | |
| ): | |
| """Grid-major exhaustive search tiled to 1024 values per program.""" | |
| return _launch_gptq_base_grid_major( | |
| observed, args, token_args, maxshrink, patience, grid, norm, chunk_size, 1024 | |
| ) | |
| def grid_search_triton_gptq_grid_major_tile2048( | |
| observed, args, token_args, maxshrink, patience, grid, norm, chunk_size | |
| ): | |
| """Grid-major exhaustive search tiled to 2048 values per program.""" | |
| return _launch_gptq_base_grid_major( | |
| observed, args, token_args, maxshrink, patience, grid, norm, chunk_size, 2048 | |
| ) | |
| def grid_search_triton_gptq_grid_major_tile4096( | |
| observed, args, token_args, maxshrink, patience, grid, norm, chunk_size | |
| ): | |
| """Grid-major exhaustive search tiled to 4096 values per program.""" | |
| return _launch_gptq_base_grid_major( | |
| observed, args, token_args, maxshrink, patience, grid, norm, chunk_size, 4096 | |
| ) | |
| def grid_search_triton_gptq_grid_major_tile8192( | |
| observed, args, token_args, maxshrink, patience, grid, norm, chunk_size | |
| ): | |
| """Grid-major exhaustive search tiled to 8192 values per program.""" | |
| return _launch_gptq_base_grid_major( | |
| observed, args, token_args, maxshrink, patience, grid, norm, chunk_size, 8192 | |
| ) | |
| def grid_search_triton_gptq_grid_major_tile16384( | |
| observed, args, token_args, maxshrink, patience, grid, norm, chunk_size | |
| ): | |
| """Grid-major exhaustive search tiled to 16384 values per program.""" | |
| return _launch_gptq_base_grid_major( | |
| observed, args, token_args, maxshrink, patience, grid, norm, chunk_size, 16384 | |
| ) | |
| def grid_search_triton_gptq_base_tile128( | |
| observed, args, token_args, maxshrink, patience, grid, norm, chunk_size | |
| ): | |
| """Group-major exhaustive search with 128 value lanes per Triton program. | |
| For group size 16 this processes eight contiguous groups per program; for | |
| group size 128 it processes one group per program. Unlike | |
| ``triton_gptq_grid_major``, each program still scans the complete grid. | |
| """ | |
| del token_args, patience, chunk_size | |
| if args.scale_dtype is not None: | |
| raise ValueError("base tile128 variant requires scale_dtype=None") | |
| min_val = torch.amin(observed, dim=(0, -1)) | |
| max_val = torch.amax(observed, dim=(0, -1)) | |
| total_steps = int(maxshrink * grid) | |
| scale_base, zp_base = calculate_qparams( | |
| min_vals=min_val, | |
| max_vals=max_val, | |
| quantization_args=args, | |
| global_scale=None, | |
| ) | |
| scale_base = scale_base.contiguous() | |
| zp_base = zp_base.to(dtype=torch.float32).contiguous() | |
| _, scale_eps = _scale_round_config(scale_base.dtype) | |
| _, num_rows, num_groups, group_size = observed.shape | |
| observed = observed.contiguous() | |
| block_g = triton.next_power_of_2(group_size) | |
| tile_groups = max(1, 128 // block_g) | |
| best_step = torch.empty( | |
| num_rows, num_groups, dtype=torch.int32, device=observed.device | |
| ) | |
| best_error = torch.empty( | |
| num_rows, num_groups, dtype=torch.float32, device=observed.device | |
| ) | |
| has_zp = not args.symmetric | |
| q_min, q_max = calculate_range(args, observed.device) | |
| _base_scale_grid_search_gptq_quant_exhaustive_tile128_kernel[ | |
| (triton.cdiv(num_rows * num_groups, tile_groups),) | |
| ]( | |
| observed, | |
| scale_base, | |
| zp_base if has_zp else scale_base, | |
| best_step, | |
| best_error, | |
| num_rows, | |
| num_groups, | |
| group_size, | |
| observed.stride(1), | |
| observed.stride(2), | |
| scale_base.stride(0), | |
| 1.0 / grid, | |
| float(q_min), | |
| float(q_max), | |
| norm, | |
| scale_eps, | |
| BLOCK_G=block_g, | |
| TILE_GROUPS=tile_groups, | |
| TOTAL_STEPS=total_steps, | |
| QUANT_TYPE=_gptq_quant_type(args), | |
| DEQUANT_DTYPE=_dequant_dtype_code(scale_base.dtype), | |
| OBSERVED_DTYPE=_observed_dtype_code(observed.dtype), | |
| HAS_ZP=has_zp, | |
| ) | |
| ps = torch.arange(total_steps, device=min_val.device, dtype=min_val.dtype) | |
| best_p = (1.0 - ps / grid)[best_step.long()] | |
| return min_val * best_p, max_val * best_p | |
| def grid_search_triton_gptq_base_p_b( | |
| observed, args, token_args, maxshrink, patience, grid, norm, chunk_size | |
| ): | |
| """Buffered base-scale patience, optionally rounding candidate scales. | |
| With ``scale_dtype=None`` this uses ordinary floating-point scales. When a | |
| scale dtype is configured, each candidate is rounded in-kernel through the | |
| same FP16/BF16/FP8/E8M0 path as the scale-rounding patience variants. | |
| """ | |
| return _launch_gptq_base_scale_patience( | |
| observed, | |
| args, | |
| token_args, | |
| maxshrink, | |
| patience, | |
| grid, | |
| norm, | |
| chunk_size, | |
| round_scale=args.scale_dtype is not None, | |
| use_patience=True, | |
| error_buffer=0.30, | |
| ) | |
| def _launch_gptq_base_p_b_tiled( | |
| observed, | |
| args, | |
| token_args, | |
| maxshrink, | |
| patience, | |
| grid, | |
| norm, | |
| chunk_size, | |
| tile_values, | |
| ): | |
| """Launch the buffered base-scale search over a fixed-size value tile.""" | |
| del token_args, chunk_size | |
| if args.scale_dtype is None: | |
| scale_round_type = 0 | |
| qparam_args = args | |
| else: | |
| scale_round_type, _ = _scale_round_config(args.scale_dtype) | |
| qparam_args = args.model_copy(update={"scale_dtype": None}) | |
| min_val = torch.amin(observed, dim=(0, -1)) | |
| max_val = torch.amax(observed, dim=(0, -1)) | |
| total_steps = int(maxshrink * grid) | |
| scale_base, zp_base = calculate_qparams( | |
| min_vals=min_val, | |
| max_vals=max_val, | |
| quantization_args=qparam_args, | |
| global_scale=None, | |
| ) | |
| if scale_round_type == 4: | |
| scale_base = torch.maximum(torch.abs(min_val), torch.abs(max_val)) | |
| scale_base = scale_base.contiguous() | |
| zp_base = zp_base.to(dtype=torch.float32).contiguous() | |
| _, scale_eps = _scale_round_config( | |
| args.scale_dtype if args.scale_dtype is not None else scale_base.dtype | |
| ) | |
| _, num_rows, num_groups, group_size = observed.shape | |
| observed = observed.contiguous() | |
| block_g = triton.next_power_of_2(group_size) | |
| tile_groups = max(1, tile_values // block_g) | |
| block_tile_groups = triton.next_power_of_2(tile_groups) | |
| best_step = torch.empty( | |
| num_rows, num_groups, dtype=torch.int32, device=observed.device | |
| ) | |
| best_error = torch.empty( | |
| num_rows, num_groups, dtype=torch.float32, device=observed.device | |
| ) | |
| has_zp = not args.symmetric | |
| q_min, q_max = calculate_range(args, observed.device) | |
| _base_scale_grid_search_gptq_quant_patience_tiled_kernel[ | |
| (triton.cdiv(num_rows * num_groups, tile_groups),) | |
| ]( | |
| observed, | |
| scale_base, | |
| zp_base if has_zp else scale_base, | |
| best_step, | |
| best_error, | |
| num_rows, | |
| num_groups, | |
| group_size, | |
| total_steps, | |
| observed.stride(1), | |
| observed.stride(2), | |
| scale_base.stride(0), | |
| 1.0 / grid, | |
| float(q_min), | |
| float(q_max), | |
| norm, | |
| patience, | |
| 0.30, | |
| scale_eps, | |
| BLOCK_G=block_g, | |
| TILE_GROUPS=tile_groups, | |
| BLOCK_TILE_GROUPS=block_tile_groups, | |
| TOTAL_STEPS=triton.next_power_of_2(total_steps), | |
| QUANT_TYPE=_gptq_quant_type(args), | |
| DEQUANT_DTYPE=_dequant_dtype_code(scale_base.dtype), | |
| OBSERVED_DTYPE=_observed_dtype_code(observed.dtype), | |
| HAS_ZP=has_zp, | |
| SCALE_ROUND_TYPE=scale_round_type, | |
| ) | |
| ps = torch.arange(total_steps, device=min_val.device, dtype=min_val.dtype) | |
| best_p = (1.0 - ps / grid)[best_step.long()] | |
| return min_val * best_p, max_val * best_p | |
| def grid_search_triton_gptq_base_p_b_tile512_adaptive( | |
| observed, args, token_args, maxshrink, patience, grid, norm, chunk_size | |
| ): | |
| """Keep every program at roughly 512 values, packing or splitting groups. | |
| Groups up to 512 values use the ordinary tiled kernel, which packs multiple | |
| complete groups into a program. Larger groups are split into independent | |
| 512-value programs; a second kernel reduces their candidate errors and | |
| applies the same per-group patience and 30% buffer policy. | |
| """ | |
| tile_values = 512 | |
| group_size = observed.shape[-1] | |
| if group_size <= tile_values: | |
| return _launch_gptq_base_p_b_tiled( | |
| observed, | |
| args, | |
| token_args, | |
| maxshrink, | |
| patience, | |
| grid, | |
| norm, | |
| chunk_size, | |
| tile_values, | |
| ) | |
| del token_args, chunk_size | |
| if args.scale_dtype is None: | |
| scale_round_type = 0 | |
| qparam_args = args | |
| else: | |
| scale_round_type, _ = _scale_round_config(args.scale_dtype) | |
| qparam_args = args.model_copy(update={"scale_dtype": None}) | |
| min_val = torch.amin(observed, dim=(0, -1)) | |
| max_val = torch.amax(observed, dim=(0, -1)) | |
| total_steps = int(maxshrink * grid) | |
| scale_base, zp_base = calculate_qparams( | |
| min_vals=min_val, | |
| max_vals=max_val, | |
| quantization_args=qparam_args, | |
| global_scale=None, | |
| ) | |
| if scale_round_type == 4: | |
| scale_base = torch.maximum(torch.abs(min_val), torch.abs(max_val)) | |
| scale_base = scale_base.contiguous() | |
| zp_base = zp_base.to(dtype=torch.float32).contiguous() | |
| _, scale_eps = _scale_round_config( | |
| args.scale_dtype if args.scale_dtype is not None else scale_base.dtype | |
| ) | |
| observed = observed.contiguous() | |
| _, num_rows, num_groups, group_size = observed.shape | |
| total_qparams = num_rows * num_groups | |
| num_chunks = triton.cdiv(group_size, tile_values) | |
| partial_errors = torch.empty( | |
| total_steps, | |
| total_qparams, | |
| num_chunks, | |
| dtype=torch.float32, | |
| device=observed.device, | |
| ) | |
| best_step = torch.empty(total_qparams, dtype=torch.int32, device=observed.device) | |
| best_error = torch.empty(total_qparams, dtype=torch.float32, device=observed.device) | |
| q_min, q_max = calculate_range(args, observed.device) | |
| has_zp = not args.symmetric | |
| _base_scale_grid_search_gptq_quant_split_error_kernel[ | |
| (total_qparams * num_chunks,) | |
| ]( | |
| observed, | |
| scale_base, | |
| zp_base if has_zp else scale_base, | |
| partial_errors, | |
| num_rows, | |
| num_groups, | |
| group_size, | |
| observed.stride(1), | |
| observed.stride(2), | |
| scale_base.stride(0), | |
| 1.0 / grid, | |
| float(q_min), | |
| float(q_max), | |
| norm, | |
| scale_eps, | |
| BLOCK_VALUES=tile_values, | |
| TOTAL_STEPS=total_steps, | |
| NUM_CHUNKS=num_chunks, | |
| QUANT_TYPE=_gptq_quant_type(args), | |
| DEQUANT_DTYPE=_dequant_dtype_code(scale_base.dtype), | |
| OBSERVED_DTYPE=_observed_dtype_code(observed.dtype), | |
| HAS_ZP=has_zp, | |
| SCALE_ROUND_TYPE=scale_round_type, | |
| ) | |
| _base_scale_grid_search_gptq_quant_reduce_split_errors_kernel[(total_qparams,)]( | |
| partial_errors, | |
| best_step, | |
| best_error, | |
| total_qparams, | |
| patience, | |
| 0.30, | |
| BLOCK_CHUNKS=triton.next_power_of_2(num_chunks), | |
| TOTAL_STEPS=total_steps, | |
| NUM_CHUNKS=num_chunks, | |
| ) | |
| ps = torch.arange(total_steps, device=min_val.device, dtype=min_val.dtype) | |
| best_p = (1.0 - ps / grid)[best_step.long()].reshape(min_val.shape) | |
| return min_val * best_p, max_val * best_p | |
| def grid_search_triton_gptq_base_p_b_tile64( | |
| observed, args, token_args, maxshrink, patience, grid, norm, chunk_size | |
| ): | |
| """Buffered base-scale search tiled to 64 values per program.""" | |
| return _launch_gptq_base_p_b_tiled( | |
| observed, args, token_args, maxshrink, patience, grid, norm, chunk_size, 64 | |
| ) | |
| def grid_search_triton_gptq_base_p_b_tile128( | |
| observed, args, token_args, maxshrink, patience, grid, norm, chunk_size | |
| ): | |
| """Buffered base-scale search tiled to 128 values per program.""" | |
| return _launch_gptq_base_p_b_tiled( | |
| observed, args, token_args, maxshrink, patience, grid, norm, chunk_size, 128 | |
| ) | |
| def grid_search_triton_gptq_base_p_b_tile256( | |
| observed, args, token_args, maxshrink, patience, grid, norm, chunk_size | |
| ): | |
| """Buffered base-scale search tiled to 256 values per program.""" | |
| return _launch_gptq_base_p_b_tiled( | |
| observed, args, token_args, maxshrink, patience, grid, norm, chunk_size, 256 | |
| ) | |
| def grid_search_triton_gptq_base_p_b_tile512( | |
| observed, args, token_args, maxshrink, patience, grid, norm, chunk_size | |
| ): | |
| """Buffered base-scale search tiled to 512 values per program.""" | |
| return _launch_gptq_base_p_b_tiled( | |
| observed, args, token_args, maxshrink, patience, grid, norm, chunk_size, 512 | |
| ) | |
| def grid_search_triton_gptq_base_p_b_tile640( | |
| observed, args, token_args, maxshrink, patience, grid, norm, chunk_size | |
| ): | |
| """Buffered base-scale search tiled to 640 values per program.""" | |
| return _launch_gptq_base_p_b_tiled( | |
| observed, args, token_args, maxshrink, patience, grid, norm, chunk_size, 640 | |
| ) | |
| def grid_search_triton_gptq_base_p_b_tile768( | |
| observed, args, token_args, maxshrink, patience, grid, norm, chunk_size | |
| ): | |
| """Buffered base-scale search tiled to 768 values per program.""" | |
| return _launch_gptq_base_p_b_tiled( | |
| observed, args, token_args, maxshrink, patience, grid, norm, chunk_size, 768 | |
| ) | |
| def grid_search_triton_gptq_base_p_b_tile896( | |
| observed, args, token_args, maxshrink, patience, grid, norm, chunk_size | |
| ): | |
| """Buffered base-scale search tiled to 896 values per program.""" | |
| return _launch_gptq_base_p_b_tiled( | |
| observed, args, token_args, maxshrink, patience, grid, norm, chunk_size, 896 | |
| ) | |
| def grid_search_triton_gptq_base_p_b_tile1024( | |
| observed, args, token_args, maxshrink, patience, grid, norm, chunk_size | |
| ): | |
| """Buffered base-scale search tiled to 1024 values per program.""" | |
| return _launch_gptq_base_p_b_tiled( | |
| observed, args, token_args, maxshrink, patience, grid, norm, chunk_size, 1024 | |
| ) | |
| def grid_search_triton_gptq_scale_round_p( | |
| observed, args, token_args, maxshrink, patience, grid, norm, chunk_size | |
| ): | |
| """Base-scale patience search with per-candidate scale-dtype rounding.""" | |
| return _launch_gptq_base_scale_patience( | |
| observed, | |
| args, | |
| token_args, | |
| maxshrink, | |
| patience, | |
| grid, | |
| norm, | |
| chunk_size, | |
| round_scale=True, | |
| ) | |
| def grid_search_triton_gptq_e8_p( | |
| observed, args, token_args, maxshrink, patience, grid, norm, chunk_size | |
| ): | |
| """MX E8M0 scale generation with GPTQ QDQ and per-group patience.""" | |
| if args.scale_dtype != torch.uint8: | |
| raise ValueError("E8M0 variant requires scale_dtype=torch.uint8") | |
| return grid_search_triton_gptq_scale_round_p( | |
| observed, | |
| args, | |
| token_args, | |
| maxshrink, | |
| patience, | |
| grid, | |
| norm, | |
| chunk_size, | |
| ) | |
| # ── Triton codebook/cutoff implementation (format-agnostic) ────────────────── | |
| @triton.jit | |
| def _fused_grid_search_cutoff_kernel( | |
| observed_ptr, | |
| scale_base_ptr, | |
| codes_ptr, | |
| cutoffs_ptr, | |
| best_step_ptr, | |
| best_error_ptr, | |
| num_rows, | |
| num_groups, | |
| group_size, | |
| total_steps, | |
| num_codes, | |
| stride_obs_row, | |
| stride_obs_group, | |
| inv_grid, | |
| norm, | |
| BLOCK_G: tl.constexpr, | |
| LOG_C: tl.constexpr, | |
| TOTAL_STEPS: tl.constexpr, | |
| ): | |
| """Codebook family: exhaustive serial search using decision cutoffs. | |
| The codebook contains the normalized representable values for the | |
| quantization format (e.g. integers for INT, irregular values for FP4). | |
| Cutoffs are midpoints between adjacent codebook entries. | |
| Shrinking by p just scales both codebook and cutoffs — equivalently, | |
| we divide observed by (scale_base * p) and bin against the fixed cutoffs. | |
| Binning uses binary search over sorted cutoffs: O(log n) comparisons | |
| per element instead of O(n). Unlike ``mindist``, quantization selects bins | |
| by precomputed midpoint cutoffs rather than comparing neighboring codes. | |
| """ | |
| pid = tl.program_id(0) | |
| row = pid // num_groups | |
| group = pid % num_groups | |
| if row >= num_rows: | |
| return | |
| g_idx = tl.arange(0, BLOCK_G) | |
| g_mask = g_idx < group_size | |
| num_cutoffs = num_codes - 1 | |
| obs = tl.load( | |
| observed_ptr + row * stride_obs_row + group * stride_obs_group + g_idx, | |
| mask=g_mask, | |
| other=0.0, | |
| ) | |
| scale_base = tl.load(scale_base_ptr + row * num_groups + group) | |
| ig = inv_grid.to(tl.float32) | |
| norm_f = norm.to(tl.float32) | |
| best_err = tl.full([], float("inf"), dtype=tl.float32) | |
| best_s = 0 | |
| for step in range(TOTAL_STEPS): | |
| if step < total_steps: | |
| p = (1.0 - step * ig).to(tl.float32) | |
| eff_scale = tl.maximum(scale_base * p, 1e-38).to(tl.float32) | |
| obs_norm = obs / eff_scale # [BLOCK_G] | |
| # Binary search over sorted cutoffs to find bin index | |
| lo = tl.zeros([BLOCK_G], dtype=tl.int32) | |
| hi = tl.full([BLOCK_G], num_cutoffs, dtype=tl.int32) | |
| for _ in range(LOG_C): | |
| mid = (lo + hi) >> 1 | |
| cut_mid = tl.load(cutoffs_ptr + mid) # gather [BLOCK_G] | |
| ge = obs_norm >= cut_mid | |
| lo = tl.where(ge, mid + 1, lo) | |
| hi = tl.where(ge, hi, mid) | |
| bin_idx = lo | |
| q_norm = tl.load(codes_ptr + bin_idx) # gather [BLOCK_G] | |
| q = q_norm * eff_scale | |
| diff = tl.abs(q - obs).to(tl.float32) | |
| diff_pow = tl.extra.cuda.libdevice.pow(diff, norm_f) | |
| err = tl.sum(tl.where(g_mask, diff_pow, 0.0), axis=0) | |
| is_better = err < best_err | |
| best_err = tl.where(is_better, err, best_err) | |
| best_s = tl.where(is_better, step, best_s) | |
| tl.store(best_step_ptr + row * num_groups + group, best_s) | |
| tl.store(best_error_ptr + row * num_groups + group, best_err) | |
| def _build_normalized_codebook(args): | |
| """Build normalized codebook and cutoffs for any quantization format. | |
| Returns (codes, cutoffs) where codes are the normalized representable | |
| values and cutoffs are midpoints between adjacent codes. For INT these | |
| are just integers; for FP4/FP8 they would be the format's representable | |
| values. | |
| """ | |
| if args.symmetric: | |
| q_min = -(2 ** (args.num_bits - 1)) | |
| q_max = 2 ** (args.num_bits - 1) - 1 | |
| else: | |
| q_min = 0 | |
| q_max = 2**args.num_bits - 1 | |
| codes = torch.arange(q_min, q_max + 1, dtype=torch.float32) | |
| cutoffs = (codes[:-1] + codes[1:]) * 0.5 | |
| return codes, cutoffs | |
| def grid_search_triton_cutoff( | |
| observed, args, token_args, maxshrink, patience, grid, norm, chunk_size | |
| ): | |
| """Format-agnostic Triton grid search using codebook + cutoffs.""" | |
| min_val = torch.amin(observed, dim=(0, -1)) | |
| max_val = torch.amax(observed, dim=(0, -1)) | |
| total_steps = int(maxshrink * grid) | |
| # Compute per-group scale at p=1.0 | |
| scale_base, _ = calculate_qparams( | |
| min_vals=min_val, | |
| max_vals=max_val, | |
| quantization_args=args, | |
| global_scale=None, | |
| ) | |
| # Build format-agnostic codebook | |
| codes, cutoffs = _build_normalized_codebook(args) | |
| codes = codes.to(device=observed.device) | |
| cutoffs = cutoffs.to(device=observed.device) | |
| _, num_rows, num_groups, group_size = observed.shape | |
| observed_contig = observed.contiguous() | |
| scale_base = scale_base.contiguous() | |
| best_step = torch.zeros( | |
| num_rows, num_groups, dtype=torch.int32, device=observed.device | |
| ) | |
| best_error = torch.full( | |
| (num_rows, num_groups), | |
| float("inf"), | |
| device=observed.device, | |
| dtype=torch.float32, | |
| ) | |
| import math | |
| num_codes = codes.shape[0] | |
| BLOCK_G = triton.next_power_of_2(group_size) | |
| num_cutoffs = num_codes - 1 | |
| LOG_C = max(1, math.ceil(math.log2(num_cutoffs))) if num_cutoffs > 0 else 1 | |
| TOTAL_STEPS = triton.next_power_of_2(total_steps) | |
| grid_launch = (num_rows * num_groups,) | |
| _fused_grid_search_cutoff_kernel[grid_launch]( | |
| observed_contig, | |
| scale_base, | |
| codes, | |
| cutoffs, | |
| best_step, | |
| best_error, | |
| num_rows, | |
| num_groups, | |
| group_size, | |
| total_steps, | |
| num_codes, | |
| observed_contig.stride(1), | |
| observed_contig.stride(2), | |
| 1.0 / grid, | |
| norm, | |
| BLOCK_G=BLOCK_G, | |
| LOG_C=LOG_C, | |
| TOTAL_STEPS=TOTAL_STEPS, | |
| ) | |
| # Reconstruct best_min/max from best step indices | |
| ps = torch.tensor( | |
| [1.0 - i / grid for i in range(total_steps)], | |
| dtype=min_val.dtype, | |
| device=min_val.device, | |
| ) | |
| best_p = ps[best_step.long()] | |
| best_min_val = min_val * best_p | |
| best_max_val = max_val * best_p | |
| return best_min_val, best_max_val | |
| # ── Triton codebook min-distance (format-agnostic, no cutoffs) ─────────────── | |
| @triton.jit | |
| def _fused_grid_search_mindist_kernel( | |
| observed_ptr, | |
| scale_base_ptr, | |
| codes_ptr, | |
| best_step_ptr, | |
| best_error_ptr, | |
| num_rows, | |
| num_groups, | |
| group_size, | |
| total_steps, | |
| num_codes, | |
| stride_obs_row, | |
| stride_obs_group, | |
| inv_grid, | |
| norm, | |
| BLOCK_G: tl.constexpr, | |
| LOG_C: tl.constexpr, | |
| TOTAL_STEPS: tl.constexpr, | |
| ): | |
| """Codebook family: exhaustive serial search by nearest-code distance. | |
| For each element, binary searches the sorted codebook to find the | |
| insertion point, then compares distances to the two adjacent codes. | |
| No cutoffs needed — just the codebook itself. Compared with ``cutoff``, it | |
| avoids a cutoff table but performs the final adjacent-distance comparison. | |
| """ | |
| pid = tl.program_id(0) | |
| row = pid // num_groups | |
| group = pid % num_groups | |
| if row >= num_rows: | |
| return | |
| g_idx = tl.arange(0, BLOCK_G) | |
| g_mask = g_idx < group_size | |
| obs = tl.load( | |
| observed_ptr + row * stride_obs_row + group * stride_obs_group + g_idx, | |
| mask=g_mask, | |
| other=0.0, | |
| ) | |
| scale_base = tl.load(scale_base_ptr + row * num_groups + group) | |
| ig = inv_grid.to(tl.float32) | |
| norm_f = norm.to(tl.float32) | |
| best_err = tl.full([], float("inf"), dtype=tl.float32) | |
| best_s = 0 | |
| for step in range(TOTAL_STEPS): | |
| if step < total_steps: | |
| p = (1.0 - step * ig).to(tl.float32) | |
| eff_scale = tl.maximum(scale_base * p, 1e-38).to(tl.float32) | |
| obs_norm = obs / eff_scale # [BLOCK_G] | |
| # Binary search for insertion point in sorted codes | |
| lo = tl.zeros([BLOCK_G], dtype=tl.int32) | |
| hi = tl.full([BLOCK_G], num_codes, dtype=tl.int32) | |
| for _ in range(LOG_C): | |
| mid = (lo + hi) >> 1 | |
| code_mid = tl.load(codes_ptr + mid) # gather [BLOCK_G] | |
| ge = obs_norm >= code_mid | |
| lo = tl.where(ge, mid + 1, lo) | |
| hi = tl.where(ge, hi, mid) | |
| # Nearest is codes[lo-1] or codes[lo], check both | |
| idx_left = tl.maximum(lo - 1, 0) | |
| idx_right = tl.minimum(lo, num_codes - 1) | |
| code_left = tl.load(codes_ptr + idx_left) | |
| code_right = tl.load(codes_ptr + idx_right) | |
| dist_left = tl.abs(obs_norm - code_left) | |
| dist_right = tl.abs(obs_norm - code_right) | |
| min_dist = tl.minimum(dist_left, dist_right) * eff_scale | |
| min_dist_pow = tl.extra.cuda.libdevice.pow(min_dist.to(tl.float32), norm_f) | |
| err = tl.sum(tl.where(g_mask, min_dist_pow, 0.0), axis=0) | |
| is_better = err < best_err | |
| best_err = tl.where(is_better, err, best_err) | |
| best_s = tl.where(is_better, step, best_s) | |
| tl.store(best_step_ptr + row * num_groups + group, best_s) | |
| tl.store(best_error_ptr + row * num_groups + group, best_err) | |
| def grid_search_triton_mindist( | |
| observed, args, token_args, maxshrink, patience, grid, norm, chunk_size | |
| ): | |
| """Format-agnostic Triton grid search using codebook min-distance.""" | |
| min_val = torch.amin(observed, dim=(0, -1)) | |
| max_val = torch.amax(observed, dim=(0, -1)) | |
| total_steps = int(maxshrink * grid) | |
| # Compute per-group scale at p=1.0 | |
| scale_base, _ = calculate_qparams( | |
| min_vals=min_val, | |
| max_vals=max_val, | |
| quantization_args=args, | |
| global_scale=None, | |
| ) | |
| # Build format-agnostic codebook (only need codes, not cutoffs) | |
| codes, _ = _build_normalized_codebook(args) | |
| codes = codes.to(device=observed.device) | |
| _, num_rows, num_groups, group_size = observed.shape | |
| observed_contig = observed.contiguous() | |
| scale_base = scale_base.contiguous() | |
| best_step = torch.zeros( | |
| num_rows, num_groups, dtype=torch.int32, device=observed.device | |
| ) | |
| best_error = torch.full( | |
| (num_rows, num_groups), | |
| float("inf"), | |
| device=observed.device, | |
| dtype=torch.float32, | |
| ) | |
| import math | |
| num_codes = codes.shape[0] | |
| BLOCK_G = triton.next_power_of_2(group_size) | |
| LOG_C = max(1, math.ceil(math.log2(num_codes))) | |
| TOTAL_STEPS = triton.next_power_of_2(total_steps) | |
| grid_launch = (num_rows * num_groups,) | |
| _fused_grid_search_mindist_kernel[grid_launch]( | |
| observed_contig, | |
| scale_base, | |
| codes, | |
| best_step, | |
| best_error, | |
| num_rows, | |
| num_groups, | |
| group_size, | |
| total_steps, | |
| num_codes, | |
| observed_contig.stride(1), | |
| observed_contig.stride(2), | |
| 1.0 / grid, | |
| norm, | |
| BLOCK_G=BLOCK_G, | |
| LOG_C=LOG_C, | |
| TOTAL_STEPS=TOTAL_STEPS, | |
| ) | |
| # Reconstruct best_min/max from best step indices | |
| ps = torch.tensor( | |
| [1.0 - i / grid for i in range(total_steps)], | |
| dtype=min_val.dtype, | |
| device=min_val.device, | |
| ) | |
| best_p = ps[best_step.long()] | |
| best_min_val = min_val * best_p | |
| best_max_val = max_val * best_p | |
| return best_min_val, best_max_val | |
| # ── Triton incremental codebook search (2-check and 3-check variants) ──────── | |
| @triton.jit | |
| def _fused_grid_search_incr2_kernel( | |
| observed_ptr, | |
| scale_base_ptr, | |
| codes_ptr, | |
| best_step_ptr, | |
| best_error_ptr, | |
| num_rows, | |
| num_groups, | |
| group_size, | |
| total_steps, | |
| num_codes, | |
| stride_obs_row, | |
| stride_obs_group, | |
| inv_grid, | |
| norm, | |
| BLOCK_G: tl.constexpr, | |
| LOG_C: tl.constexpr, | |
| TOTAL_STEPS: tl.constexpr, | |
| ): | |
| """Incremental-codebook family: track bins with one directional neighbor. | |
| Performs a full binary search at step 0, then checks | |
| 2 codes (current + 1 directional neighbor) per subsequent step. | |
| Since p decreases monotonically, |obs_norm| increases — positive values | |
| shift right in the codebook, negative values shift left. | |
| """ | |
| pid = tl.program_id(0) | |
| row = pid // num_groups | |
| group = pid % num_groups | |
| if row >= num_rows: | |
| return | |
| g_idx = tl.arange(0, BLOCK_G) | |
| g_mask = g_idx < group_size | |
| obs = tl.load( | |
| observed_ptr + row * stride_obs_row + group * stride_obs_group + g_idx, | |
| mask=g_mask, | |
| other=0.0, | |
| ) | |
| scale_base = tl.load(scale_base_ptr + row * num_groups + group) | |
| ig = inv_grid.to(tl.float32) | |
| norm_f = norm.to(tl.float32) | |
| # Precompute shift direction: +1 for positive obs, -1 for negative | |
| shift_dir = tl.where(obs >= 0, 1, -1) | |
| best_err = tl.full([], float("inf"), dtype=tl.float32) | |
| best_s = 0 | |
| bin_idx = tl.zeros([BLOCK_G], dtype=tl.int32) | |
| for step in range(TOTAL_STEPS): | |
| if step < total_steps: | |
| p = (1.0 - step * ig).to(tl.float32) | |
| eff_scale = tl.maximum(scale_base * p, 1e-38).to(tl.float32) | |
| obs_norm = obs / eff_scale | |
| if step == 0: | |
| # Full binary search for insertion point | |
| lo = tl.zeros([BLOCK_G], dtype=tl.int32) | |
| hi = tl.full([BLOCK_G], num_codes, dtype=tl.int32) | |
| for _ in range(LOG_C): | |
| mid = (lo + hi) >> 1 | |
| code_mid = tl.load(codes_ptr + mid) | |
| ge = obs_norm >= code_mid | |
| lo = tl.where(ge, mid + 1, lo) | |
| hi = tl.where(ge, hi, mid) | |
| idx_left = tl.maximum(lo - 1, 0) | |
| idx_right = tl.minimum(lo, num_codes - 1) | |
| d_left = tl.abs(obs_norm - tl.load(codes_ptr + idx_left)) | |
| d_right = tl.abs(obs_norm - tl.load(codes_ptr + idx_right)) | |
| left_wins = d_left <= d_right | |
| bin_idx = tl.where(left_wins, idx_left, idx_right) | |
| min_dist = tl.minimum(d_left, d_right) * eff_scale | |
| else: | |
| # Incremental: check current and 1 neighbor in shift direction | |
| d_cur = tl.abs(obs_norm - tl.load(codes_ptr + bin_idx)) | |
| shift_idx = tl.maximum( | |
| tl.minimum(bin_idx + shift_dir, num_codes - 1), 0 | |
| ) | |
| d_shift = tl.abs(obs_norm - tl.load(codes_ptr + shift_idx)) | |
| better = d_shift < d_cur | |
| bin_idx = tl.where(better, shift_idx, bin_idx) | |
| min_dist = tl.minimum(d_cur, d_shift) * eff_scale | |
| min_dist_pow = tl.extra.cuda.libdevice.pow(min_dist.to(tl.float32), norm_f) | |
| err = tl.sum(tl.where(g_mask, min_dist_pow, 0.0), axis=0) | |
| is_better = err < best_err | |
| best_err = tl.where(is_better, err, best_err) | |
| best_s = tl.where(is_better, step, best_s) | |
| tl.store(best_step_ptr + row * num_groups + group, best_s) | |
| tl.store(best_error_ptr + row * num_groups + group, best_err) | |
| @triton.jit | |
| def _fused_grid_search_incr3_kernel( | |
| observed_ptr, | |
| scale_base_ptr, | |
| codes_ptr, | |
| best_step_ptr, | |
| best_error_ptr, | |
| num_rows, | |
| num_groups, | |
| group_size, | |
| total_steps, | |
| num_codes, | |
| stride_obs_row, | |
| stride_obs_group, | |
| inv_grid, | |
| norm, | |
| BLOCK_G: tl.constexpr, | |
| LOG_C: tl.constexpr, | |
| TOTAL_STEPS: tl.constexpr, | |
| ): | |
| """Incremental-codebook family: track bins with two directional neighbors. | |
| Performs a full binary search at step 0, then checks | |
| 3 codes (current + 2 in shift direction) per subsequent step. | |
| Since |obs_norm| only increases, the shift is always away from zero: | |
| positive values shift right, negative shift left. | |
| """ | |
| pid = tl.program_id(0) | |
| row = pid // num_groups | |
| group = pid % num_groups | |
| if row >= num_rows: | |
| return | |
| g_idx = tl.arange(0, BLOCK_G) | |
| g_mask = g_idx < group_size | |
| obs = tl.load( | |
| observed_ptr + row * stride_obs_row + group * stride_obs_group + g_idx, | |
| mask=g_mask, | |
| other=0.0, | |
| ) | |
| scale_base = tl.load(scale_base_ptr + row * num_groups + group) | |
| ig = inv_grid.to(tl.float32) | |
| norm_f = norm.to(tl.float32) | |
| shift_dir = tl.where(obs >= 0, 1, -1) | |
| best_err = tl.full([], float("inf"), dtype=tl.float32) | |
| best_s = 0 | |
| bin_idx = tl.zeros([BLOCK_G], dtype=tl.int32) | |
| for step in range(TOTAL_STEPS): | |
| if step < total_steps: | |
| p = (1.0 - step * ig).to(tl.float32) | |
| eff_scale = tl.maximum(scale_base * p, 1e-38).to(tl.float32) | |
| obs_norm = obs / eff_scale | |
| if step == 0: | |
| lo = tl.zeros([BLOCK_G], dtype=tl.int32) | |
| hi = tl.full([BLOCK_G], num_codes, dtype=tl.int32) | |
| for _ in range(LOG_C): | |
| mid = (lo + hi) >> 1 | |
| code_mid = tl.load(codes_ptr + mid) | |
| ge = obs_norm >= code_mid | |
| lo = tl.where(ge, mid + 1, lo) | |
| hi = tl.where(ge, hi, mid) | |
| idx_left = tl.maximum(lo - 1, 0) | |
| idx_right = tl.minimum(lo, num_codes - 1) | |
| d_left = tl.abs(obs_norm - tl.load(codes_ptr + idx_left)) | |
| d_right = tl.abs(obs_norm - tl.load(codes_ptr + idx_right)) | |
| left_wins = d_left <= d_right | |
| bin_idx = tl.where(left_wins, idx_left, idx_right) | |
| min_dist = tl.minimum(d_left, d_right) * eff_scale | |
| else: | |
| # Check current + 2 neighbors in shift direction | |
| idx_s1 = tl.maximum(tl.minimum(bin_idx + shift_dir, num_codes - 1), 0) | |
| idx_s2 = tl.maximum( | |
| tl.minimum(bin_idx + shift_dir * 2, num_codes - 1), 0 | |
| ) | |
| d_cur = tl.abs(obs_norm - tl.load(codes_ptr + bin_idx)) | |
| d_s1 = tl.abs(obs_norm - tl.load(codes_ptr + idx_s1)) | |
| d_s2 = tl.abs(obs_norm - tl.load(codes_ptr + idx_s2)) | |
| new_bin = bin_idx | |
| new_d = d_cur | |
| better_s1 = d_s1 < new_d | |
| new_bin = tl.where(better_s1, idx_s1, new_bin) | |
| new_d = tl.where(better_s1, d_s1, new_d) | |
| better_s2 = d_s2 < new_d | |
| new_bin = tl.where(better_s2, idx_s2, new_bin) | |
| new_d = tl.where(better_s2, d_s2, new_d) | |
| bin_idx = new_bin | |
| min_dist = new_d * eff_scale | |
| min_dist_pow = tl.extra.cuda.libdevice.pow(min_dist.to(tl.float32), norm_f) | |
| err = tl.sum(tl.where(g_mask, min_dist_pow, 0.0), axis=0) | |
| is_better = err < best_err | |
| best_err = tl.where(is_better, err, best_err) | |
| best_s = tl.where(is_better, step, best_s) | |
| tl.store(best_step_ptr + row * num_groups + group, best_s) | |
| tl.store(best_error_ptr + row * num_groups + group, best_err) | |
| @triton.jit | |
| def _fused_grid_search_incrN_patience_kernel( | |
| observed_ptr, | |
| scale_base_ptr, | |
| codes_ptr, | |
| best_step_ptr, | |
| best_error_ptr, | |
| num_rows, | |
| num_groups, | |
| group_size, | |
| total_steps, | |
| num_codes, | |
| stride_obs_row, | |
| stride_obs_group, | |
| inv_grid, | |
| norm, | |
| patience, | |
| BLOCK_G: tl.constexpr, | |
| LOG_C: tl.constexpr, | |
| TOTAL_STEPS: tl.constexpr, | |
| NUM_NEIGHBORS: tl.constexpr, | |
| ): | |
| """Incremental-codebook family: configurable neighbors plus patience. | |
| NUM_NEIGHBORS controls how many codes to check in the shift direction | |
| after the initial binary search at step 0. Unlike the exhaustive incr2/3 | |
| siblings, it stops each group after its error fails to improve for the | |
| requested patience. | |
| """ | |
| pid = tl.program_id(0) | |
| row = pid // num_groups | |
| group = pid % num_groups | |
| if row >= num_rows: | |
| return | |
| g_idx = tl.arange(0, BLOCK_G) | |
| g_mask = g_idx < group_size | |
| obs = tl.load( | |
| observed_ptr + row * stride_obs_row + group * stride_obs_group + g_idx, | |
| mask=g_mask, | |
| other=0.0, | |
| ) | |
| scale_base = tl.load(scale_base_ptr + row * num_groups + group) | |
| ig = inv_grid.to(tl.float32) | |
| norm_f = norm.to(tl.float32) | |
| shift_dir = tl.where(obs >= 0, 1, -1) | |
| best_err = tl.full([], float("inf"), dtype=tl.float32) | |
| best_s = 0 | |
| bin_idx = tl.zeros([BLOCK_G], dtype=tl.int32) | |
| patience_ctr = 0 | |
| for step in range(TOTAL_STEPS): | |
| if step < total_steps: | |
| if patience_ctr < patience: | |
| p = (1.0 - step * ig).to(tl.float32) | |
| eff_scale = tl.maximum(scale_base * p, 1e-38).to(tl.float32) | |
| obs_norm = obs / eff_scale | |
| if step == 0: | |
| lo = tl.zeros([BLOCK_G], dtype=tl.int32) | |
| hi = tl.full([BLOCK_G], num_codes, dtype=tl.int32) | |
| for _ in range(LOG_C): | |
| mid = (lo + hi) >> 1 | |
| code_mid = tl.load(codes_ptr + mid) | |
| ge = obs_norm >= code_mid | |
| lo = tl.where(ge, mid + 1, lo) | |
| hi = tl.where(ge, hi, mid) | |
| idx_left = tl.maximum(lo - 1, 0) | |
| idx_right = tl.minimum(lo, num_codes - 1) | |
| d_left = tl.abs(obs_norm - tl.load(codes_ptr + idx_left)) | |
| d_right = tl.abs(obs_norm - tl.load(codes_ptr + idx_right)) | |
| left_wins = d_left <= d_right | |
| bin_idx = tl.where(left_wins, idx_left, idx_right) | |
| min_dist = tl.minimum(d_left, d_right) * eff_scale | |
| else: | |
| # Check current + NUM_NEIGHBORS in shift direction | |
| new_bin = bin_idx | |
| new_d = tl.abs(obs_norm - tl.load(codes_ptr + bin_idx)) | |
| for k in tl.static_range(1, NUM_NEIGHBORS + 1): | |
| idx_k = tl.maximum( | |
| tl.minimum(bin_idx + shift_dir * k, num_codes - 1), 0 | |
| ) | |
| d_k = tl.abs(obs_norm - tl.load(codes_ptr + idx_k)) | |
| better_k = d_k < new_d | |
| new_bin = tl.where(better_k, idx_k, new_bin) | |
| new_d = tl.where(better_k, d_k, new_d) | |
| bin_idx = new_bin | |
| min_dist = new_d * eff_scale | |
| min_dist_pow = tl.extra.cuda.libdevice.pow( | |
| min_dist.to(tl.float32), norm_f | |
| ) | |
| err = tl.sum(tl.where(g_mask, min_dist_pow, 0.0), axis=0) | |
| is_better = err < best_err | |
| best_err = tl.where(is_better, err, best_err) | |
| best_s = tl.where(is_better, step, best_s) | |
| patience_ctr = tl.where(is_better, 0, patience_ctr + 1) | |
| tl.store(best_step_ptr + row * num_groups + group, best_s) | |
| tl.store(best_error_ptr + row * num_groups + group, best_err) | |
| def _launch_incrN_patience( | |
| observed, args, maxshrink, patience, grid, norm, num_neighbors | |
| ): | |
| """Shared launcher for incrN patience kernel with variable neighbor count.""" | |
| import math | |
| min_val = torch.amin(observed, dim=(0, -1)) | |
| max_val = torch.amax(observed, dim=(0, -1)) | |
| total_steps = int(maxshrink * grid) | |
| scale_base, _ = calculate_qparams( | |
| min_vals=min_val, | |
| max_vals=max_val, | |
| quantization_args=args, | |
| global_scale=None, | |
| ) | |
| codes, _ = _build_normalized_codebook(args) | |
| codes = codes.to(device=observed.device) | |
| _, num_rows, num_groups, group_size = observed.shape | |
| observed_contig = observed.contiguous() | |
| scale_base = scale_base.contiguous() | |
| best_step = torch.zeros( | |
| num_rows, num_groups, dtype=torch.int32, device=observed.device | |
| ) | |
| best_error = torch.full( | |
| (num_rows, num_groups), | |
| float("inf"), | |
| device=observed.device, | |
| dtype=torch.float32, | |
| ) | |
| num_codes = codes.shape[0] | |
| BLOCK_G = triton.next_power_of_2(group_size) | |
| LOG_C = max(1, math.ceil(math.log2(num_codes))) | |
| TOTAL_STEPS = triton.next_power_of_2(total_steps) | |
| grid_launch = (num_rows * num_groups,) | |
| _fused_grid_search_incrN_patience_kernel[grid_launch]( | |
| observed_contig, | |
| scale_base, | |
| codes, | |
| best_step, | |
| best_error, | |
| num_rows, | |
| num_groups, | |
| group_size, | |
| total_steps, | |
| num_codes, | |
| observed_contig.stride(1), | |
| observed_contig.stride(2), | |
| 1.0 / grid, | |
| norm, | |
| patience, | |
| BLOCK_G=BLOCK_G, | |
| LOG_C=LOG_C, | |
| TOTAL_STEPS=TOTAL_STEPS, | |
| NUM_NEIGHBORS=num_neighbors, | |
| ) | |
| ps = torch.tensor( | |
| [1.0 - i / grid for i in range(total_steps)], | |
| dtype=min_val.dtype, | |
| device=min_val.device, | |
| ) | |
| best_p = ps[best_step.long()] | |
| return min_val * best_p, max_val * best_p | |
| def grid_search_triton_incrNp1( | |
| observed, args, token_args, maxshrink, patience, grid, norm, chunk_size | |
| ): | |
| return _launch_incrN_patience(observed, args, maxshrink, patience, grid, norm, 1) | |
| def grid_search_triton_incrNp2( | |
| observed, args, token_args, maxshrink, patience, grid, norm, chunk_size | |
| ): | |
| return _launch_incrN_patience(observed, args, maxshrink, patience, grid, norm, 2) | |
| def grid_search_triton_incrNp3( | |
| observed, args, token_args, maxshrink, patience, grid, norm, chunk_size | |
| ): | |
| return _launch_incrN_patience(observed, args, maxshrink, patience, grid, norm, 3) | |
| def grid_search_triton_incrNp4( | |
| observed, args, token_args, maxshrink, patience, grid, norm, chunk_size | |
| ): | |
| return _launch_incrN_patience(observed, args, maxshrink, patience, grid, norm, 4) | |
| def _launch_incremental_kernel(kernel, observed, args, maxshrink, grid, norm): | |
| """Shared launcher for incremental kernels.""" | |
| import math | |
| min_val = torch.amin(observed, dim=(0, -1)) | |
| max_val = torch.amax(observed, dim=(0, -1)) | |
| total_steps = int(maxshrink * grid) | |
| scale_base, _ = calculate_qparams( | |
| min_vals=min_val, | |
| max_vals=max_val, | |
| quantization_args=args, | |
| global_scale=None, | |
| ) | |
| codes, _ = _build_normalized_codebook(args) | |
| codes = codes.to(device=observed.device) | |
| _, num_rows, num_groups, group_size = observed.shape | |
| observed_contig = observed.contiguous() | |
| scale_base = scale_base.contiguous() | |
| best_step = torch.zeros( | |
| num_rows, num_groups, dtype=torch.int32, device=observed.device | |
| ) | |
| best_error = torch.full( | |
| (num_rows, num_groups), | |
| float("inf"), | |
| device=observed.device, | |
| dtype=torch.float32, | |
| ) | |
| num_codes = codes.shape[0] | |
| BLOCK_G = triton.next_power_of_2(group_size) | |
| LOG_C = max(1, math.ceil(math.log2(num_codes))) | |
| TOTAL_STEPS = triton.next_power_of_2(total_steps) | |
| grid_launch = (num_rows * num_groups,) | |
| kernel[grid_launch]( | |
| observed_contig, | |
| scale_base, | |
| codes, | |
| best_step, | |
| best_error, | |
| num_rows, | |
| num_groups, | |
| group_size, | |
| total_steps, | |
| num_codes, | |
| observed_contig.stride(1), | |
| observed_contig.stride(2), | |
| 1.0 / grid, | |
| norm, | |
| BLOCK_G=BLOCK_G, | |
| LOG_C=LOG_C, | |
| TOTAL_STEPS=TOTAL_STEPS, | |
| ) | |
| ps = torch.tensor( | |
| [1.0 - i / grid for i in range(total_steps)], | |
| dtype=min_val.dtype, | |
| device=min_val.device, | |
| ) | |
| best_p = ps[best_step.long()] | |
| return min_val * best_p, max_val * best_p | |
| def grid_search_triton_incr2( | |
| observed, args, token_args, maxshrink, patience, grid, norm, chunk_size | |
| ): | |
| """Incremental search: binary search at step 0, 2 checks after.""" | |
| return _launch_incremental_kernel( | |
| _fused_grid_search_incr2_kernel, observed, args, maxshrink, grid, norm | |
| ) | |
| def grid_search_triton_incr3( | |
| observed, args, token_args, maxshrink, patience, grid, norm, chunk_size | |
| ): | |
| """Incremental search: binary search at step 0, 3 checks after.""" | |
| return _launch_incremental_kernel( | |
| _fused_grid_search_incr3_kernel, observed, args, maxshrink, grid, norm | |
| ) | |
| # ── Triton multi-group codebook min-distance ───────────────────────────────── | |
| @triton.jit | |
| def _fused_grid_search_multigroup_kernel( | |
| observed_ptr, | |
| scale_base_ptr, | |
| codes_ptr, | |
| best_step_ptr, | |
| best_error_ptr, | |
| num_rows, | |
| num_groups, | |
| group_size, | |
| total_steps, | |
| num_codes, | |
| stride_obs_row, | |
| stride_obs_group, | |
| inv_grid, | |
| norm, | |
| total_work, | |
| BLOCK_G: tl.constexpr, | |
| LOG_C: tl.constexpr, | |
| TOTAL_STEPS: tl.constexpr, | |
| GROUPS_PER_PID: tl.constexpr, | |
| ): | |
| """Codebook family: nearest-distance search batching groups per program. | |
| Quantization matches ``mindist``; the difference is launch geometry. Each | |
| program processes ``GROUPS_PER_PID`` groups serially to amortize program | |
| overhead, rather than assigning exactly one program to each group. | |
| """ | |
| pid = tl.program_id(0) | |
| base_work = pid * GROUPS_PER_PID | |
| g_idx = tl.arange(0, BLOCK_G) | |
| g_mask = g_idx < group_size | |
| ig = inv_grid.to(tl.float32) | |
| norm_f = norm.to(tl.float32) | |
| for g_off in range(GROUPS_PER_PID): | |
| work_idx = base_work + g_off | |
| valid = work_idx < total_work | |
| if valid: | |
| row = work_idx // num_groups | |
| group = work_idx % num_groups | |
| obs = tl.load( | |
| observed_ptr + row * stride_obs_row + group * stride_obs_group + g_idx, | |
| mask=g_mask, | |
| other=0.0, | |
| ) | |
| scale_base = tl.load(scale_base_ptr + row * num_groups + group) | |
| best_err = tl.full([], float("inf"), dtype=tl.float32) | |
| best_s = 0 | |
| for step in range(TOTAL_STEPS): | |
| if step < total_steps: | |
| p = (1.0 - step * ig).to(tl.float32) | |
| eff_scale = tl.maximum(scale_base * p, 1e-38).to(tl.float32) | |
| obs_norm = obs / eff_scale | |
| lo = tl.zeros([BLOCK_G], dtype=tl.int32) | |
| hi = tl.full([BLOCK_G], num_codes, dtype=tl.int32) | |
| for _ in range(LOG_C): | |
| mid = (lo + hi) >> 1 | |
| code_mid = tl.load(codes_ptr + mid) | |
| ge = obs_norm >= code_mid | |
| lo = tl.where(ge, mid + 1, lo) | |
| hi = tl.where(ge, hi, mid) | |
| idx_left = tl.maximum(lo - 1, 0) | |
| idx_right = tl.minimum(lo, num_codes - 1) | |
| code_left = tl.load(codes_ptr + idx_left) | |
| code_right = tl.load(codes_ptr + idx_right) | |
| dist_left = tl.abs(obs_norm - code_left) | |
| dist_right = tl.abs(obs_norm - code_right) | |
| min_dist = tl.minimum(dist_left, dist_right) * eff_scale | |
| min_dist_pow = tl.extra.cuda.libdevice.pow( | |
| min_dist.to(tl.float32), norm_f | |
| ) | |
| err = tl.sum(tl.where(g_mask, min_dist_pow, 0.0), axis=0) | |
| is_better = err < best_err | |
| best_err = tl.where(is_better, err, best_err) | |
| best_s = tl.where(is_better, step, best_s) | |
| tl.store(best_step_ptr + row * num_groups + group, best_s) | |
| tl.store(best_error_ptr + row * num_groups + group, best_err) | |
| def grid_search_triton_multigroup( | |
| observed, args, token_args, maxshrink, patience, grid, norm, chunk_size | |
| ): | |
| """Format-agnostic Triton grid search — multiple groups per program.""" | |
| min_val = torch.amin(observed, dim=(0, -1)) | |
| max_val = torch.amax(observed, dim=(0, -1)) | |
| total_steps = int(maxshrink * grid) | |
| scale_base, _ = calculate_qparams( | |
| min_vals=min_val, | |
| max_vals=max_val, | |
| quantization_args=args, | |
| global_scale=None, | |
| ) | |
| codes, _ = _build_normalized_codebook(args) | |
| codes = codes.to(device=observed.device) | |
| _, num_rows, num_groups, group_size = observed.shape | |
| observed_contig = observed.contiguous() | |
| scale_base = scale_base.contiguous() | |
| best_step = torch.zeros( | |
| num_rows, num_groups, dtype=torch.int32, device=observed.device | |
| ) | |
| best_error = torch.full( | |
| (num_rows, num_groups), | |
| float("inf"), | |
| device=observed.device, | |
| dtype=torch.float32, | |
| ) | |
| import math | |
| num_codes = codes.shape[0] | |
| BLOCK_G = triton.next_power_of_2(group_size) | |
| LOG_C = max(1, math.ceil(math.log2(num_codes))) | |
| TOTAL_STEPS = triton.next_power_of_2(total_steps) | |
| total_work = num_rows * num_groups | |
| GROUPS_PER_PID = 4 | |
| num_pids = math.ceil(total_work / GROUPS_PER_PID) | |
| grid_launch = (num_pids,) | |
| _fused_grid_search_multigroup_kernel[grid_launch]( | |
| observed_contig, | |
| scale_base, | |
| codes, | |
| best_step, | |
| best_error, | |
| num_rows, | |
| num_groups, | |
| group_size, | |
| total_steps, | |
| num_codes, | |
| observed_contig.stride(1), | |
| observed_contig.stride(2), | |
| 1.0 / grid, | |
| norm, | |
| total_work, | |
| BLOCK_G=BLOCK_G, | |
| LOG_C=LOG_C, | |
| TOTAL_STEPS=TOTAL_STEPS, | |
| GROUPS_PER_PID=GROUPS_PER_PID, | |
| ) | |
| ps = torch.tensor( | |
| [1.0 - i / grid for i in range(total_steps)], | |
| dtype=min_val.dtype, | |
| device=min_val.device, | |
| ) | |
| best_p = ps[best_step.long()] | |
| best_min_val = min_val * best_p | |
| best_max_val = max_val * best_p | |
| return best_min_val, best_max_val | |
| # ── Benchmark infrastructure ───────────────────────────────────────────────── | |
| def make_observer_inputs( | |
| rows, | |
| cols, | |
| strategy, | |
| group_size, | |
| num_bits, | |
| device, | |
| scale_dtype=None, | |
| quant_type=QuantizationType.INT, | |
| ): | |
| """Create a weight tensor and flatten it for calibration.""" | |
| torch.manual_seed(42) | |
| module = torch.nn.Linear(cols, rows, bias=False, device=device) | |
| if strategy == "group": | |
| quant_args = QuantizationArgs( | |
| num_bits=num_bits, | |
| type=quant_type, | |
| symmetric=True, | |
| strategy=QuantizationStrategy.GROUP, | |
| group_size=group_size, | |
| scale_dtype=scale_dtype, | |
| ) | |
| elif strategy == "channel": | |
| quant_args = QuantizationArgs( | |
| num_bits=num_bits, | |
| type=quant_type, | |
| symmetric=True, | |
| strategy=QuantizationStrategy.CHANNEL, | |
| scale_dtype=scale_dtype, | |
| ) | |
| elif strategy == "tensor": | |
| quant_args = QuantizationArgs( | |
| num_bits=num_bits, | |
| type=quant_type, | |
| symmetric=True, | |
| strategy=QuantizationStrategy.TENSOR, | |
| scale_dtype=scale_dtype, | |
| ) | |
| else: | |
| raise ValueError(f"Unknown strategy: {strategy}") | |
| token_args = quant_args.model_copy(update={"strategy": QuantizationStrategy.TOKEN}) | |
| observed = flatten_for_calibration(module.weight, "weight", quant_args) | |
| return observed, quant_args, token_args | |
| def time_fn(fn, inputs, warmup, iters): | |
| """Time a grid search function.""" | |
| observed, args, token_args, maxshrink, patience, grid, norm, chunk_size = inputs | |
| is_cuda = observed.is_cuda | |
| for _ in range(warmup): | |
| fn(observed, args, token_args, maxshrink, patience, grid, norm, chunk_size) | |
| if is_cuda: | |
| torch.cuda.synchronize() | |
| if is_cuda: | |
| torch.cuda.synchronize() | |
| torch.cuda.reset_peak_memory_stats() | |
| mem_before = torch.cuda.max_memory_allocated() | |
| times = [] | |
| for _ in range(iters): | |
| if is_cuda: | |
| torch.cuda.synchronize() | |
| t0 = time.perf_counter() | |
| fn(observed, args, token_args, maxshrink, patience, grid, norm, chunk_size) | |
| if is_cuda: | |
| torch.cuda.synchronize() | |
| times.append(time.perf_counter() - t0) | |
| peak_delta_mb = 0.0 | |
| if is_cuda: | |
| peak_delta_mb = (torch.cuda.max_memory_allocated() - mem_before) / (1024 * 1024) | |
| return times, peak_delta_mb | |
| def main(): | |
| parser = argparse.ArgumentParser(description="Benchmark MSE observer grid search") | |
| parser.add_argument( | |
| "--device", default="cuda" if torch.cuda.is_available() else "cpu" | |
| ) | |
| parser.add_argument("--rows", type=int, default=14336) | |
| parser.add_argument("--cols", type=int, default=256 * 16) | |
| parser.add_argument("--group-size", type=int, default=16) | |
| parser.add_argument("--num-bits", type=int, default=4) | |
| parser.add_argument("--quant-type", default="float", choices=["int", "float"]) | |
| parser.add_argument( | |
| "--scale-dtype", | |
| default="none", | |
| choices=["none", "float16", "bfloat16", "float8_e4m3fn", "uint8"], | |
| ) | |
| parser.add_argument( | |
| "--strategy", default="group", choices=["group", "channel", "tensor"] | |
| ) | |
| parser.add_argument("--maxshrink", type=float, default=0.20) | |
| parser.add_argument("--patience", type=int, default=5) | |
| parser.add_argument("--grid", type=float, default=100.0) | |
| parser.add_argument("--norm", type=float, default=2.4) | |
| parser.add_argument("--chunk-size", type=int, default=5) | |
| parser.add_argument("--hier-n", type=int, default=16) | |
| parser.add_argument("--hier-keep-num", type=int, default=1) | |
| parser.add_argument("--hier-keep-den", type=int, default=4) | |
| parser.add_argument("--hier-m", type=int, default=2) | |
| parser.add_argument( | |
| "--hier-k", | |
| type=int, | |
| default=3, | |
| help="Number of refinement generations after the initial candidates", | |
| ) | |
| parser.add_argument("--hier-p-min", type=float, default=0.8) | |
| parser.add_argument("--hier-p-max", type=float, default=1.8) | |
| parser.add_argument("--hier-fast-pow", action="store_true") | |
| parser.add_argument("--warmup", type=int, default=WARMUP) | |
| parser.add_argument("--iters", type=int, default=ITERS) | |
| parser.add_argument( | |
| "--variants", | |
| nargs="+", | |
| default=None, | |
| help="Optional benchmark variant names to run", | |
| ) | |
| args = parser.parse_args() | |
| HIER_CONFIG.update( | |
| n=args.hier_n, | |
| keep_num=args.hier_keep_num, | |
| keep_den=args.hier_keep_den, | |
| m=args.hier_m, | |
| k=args.hier_k, | |
| p_min=args.hier_p_min, | |
| p_max=args.hier_p_max, | |
| fast_pow=args.hier_fast_pow, | |
| ) | |
| scale_dtype = { | |
| "none": None, | |
| "float16": torch.float16, | |
| "bfloat16": torch.bfloat16, | |
| "float8_e4m3fn": torch.float8_e4m3fn, | |
| "uint8": torch.uint8, | |
| }[args.scale_dtype] | |
| quant_type = { | |
| "int": QuantizationType.INT, | |
| "float": QuantizationType.FLOAT, | |
| }[args.quant_type] | |
| total_steps = int(args.maxshrink * args.grid) | |
| print(f"Device: {args.device}") | |
| print(f"Weight: ({args.rows}, {args.cols})") | |
| print(f"Strategy: {args.strategy}") | |
| print(f"Group size: {args.group_size}") | |
| print(f"Bits: {args.num_bits}") | |
| print(f"Quant type: {args.quant_type}") | |
| print(f"Scale dtype: {args.scale_dtype}") | |
| print(f"Maxshrink: {args.maxshrink}") | |
| print(f"Grid: {args.grid}") | |
| print(f"Total steps: {total_steps}") | |
| print(f"Patience: {args.patience}") | |
| print(f"Norm: {args.norm}") | |
| print(f"Chunk size: {args.chunk_size}") | |
| hierarchical_counts = [args.hier_n] | |
| for _ in range(args.hier_k): | |
| hierarchical_counts.append( | |
| hierarchical_counts[-1] | |
| * args.hier_keep_num | |
| // args.hier_keep_den | |
| * args.hier_m | |
| ) | |
| print( | |
| "Hierarchical: " | |
| f"counts={hierarchical_counts}, total={sum(hierarchical_counts)}, " | |
| f"range=[{args.hier_p_min}, {args.hier_p_max}]" | |
| ) | |
| print(f"Hier fast pow: {args.hier_fast_pow}") | |
| print(f"Warmup: {args.warmup} Iters: {args.iters}") | |
| print() | |
| observed, quant_args, token_args = make_observer_inputs( | |
| args.rows, | |
| args.cols, | |
| args.strategy, | |
| args.group_size, | |
| args.num_bits, | |
| args.device, | |
| scale_dtype, | |
| quant_type, | |
| ) | |
| print(f"Observed shape: {observed.shape}") | |
| print() | |
| inputs = ( | |
| observed, | |
| quant_args, | |
| token_args, | |
| args.maxshrink, | |
| args.patience, | |
| args.grid, | |
| args.norm, | |
| args.chunk_size, | |
| ) | |
| if scale_dtype is None: | |
| scale_variant = ("triton_gptq_base_p", grid_search_triton_gptq_base_p) | |
| elif scale_dtype == torch.uint8: | |
| scale_variant = ("triton_gptq_e8_p", grid_search_triton_gptq_e8_p) | |
| else: | |
| scale_variant = ( | |
| "triton_gptq_scale_round_p", | |
| grid_search_triton_gptq_scale_round_p, | |
| ) | |
| variants = [ | |
| ("eager", grid_search_eager), | |
| ("eager_no_patience", grid_search_eager_no_patience), | |
| # ("compiled", grid_search_compiled), | |
| ("triton", grid_search_triton), | |
| ("triton_gptq_quant", grid_search_triton_gptq_quant), | |
| ("triton_gptq_quant_p", grid_search_triton_gptq_quant_p), | |
| ("triton_gptq_vec", grid_search_triton_gptq_quant_vec), | |
| ("triton_gptq_base", grid_search_triton_gptq_base), | |
| ("triton_gptq_grid_major", grid_search_triton_gptq_grid_major), | |
| ("triton_gptq_grid_major_tile64", grid_search_triton_gptq_grid_major_tile64), | |
| ("triton_gptq_grid_major_tile128", grid_search_triton_gptq_grid_major_tile128), | |
| ("triton_gptq_grid_major_tile256", grid_search_triton_gptq_grid_major_tile256), | |
| ("triton_gptq_grid_major_tile512", grid_search_triton_gptq_grid_major_tile512), | |
| ( | |
| "triton_gptq_grid_major_tile1024", | |
| grid_search_triton_gptq_grid_major_tile1024, | |
| ), | |
| ( | |
| "triton_gptq_grid_major_tile2048", | |
| grid_search_triton_gptq_grid_major_tile2048, | |
| ), | |
| ( | |
| "triton_gptq_grid_major_tile4096", | |
| grid_search_triton_gptq_grid_major_tile4096, | |
| ), | |
| ( | |
| "triton_gptq_grid_major_tile8192", | |
| grid_search_triton_gptq_grid_major_tile8192, | |
| ), | |
| ( | |
| "triton_gptq_grid_major_tile16384", | |
| grid_search_triton_gptq_grid_major_tile16384, | |
| ), | |
| ("triton_gptq_base_tile128", grid_search_triton_gptq_base_tile128), | |
| scale_variant, | |
| ("triton_gptq_base_p_b", grid_search_triton_gptq_base_p_b), | |
| ("triton_gptq_base_p_b_tile64", grid_search_triton_gptq_base_p_b_tile64), | |
| ("triton_gptq_base_p_b_tile128", grid_search_triton_gptq_base_p_b_tile128), | |
| ("triton_gptq_base_p_b_tile256", grid_search_triton_gptq_base_p_b_tile256), | |
| ("triton_gptq_base_p_b_tile512", grid_search_triton_gptq_base_p_b_tile512), | |
| ( | |
| "triton_gptq_base_p_b_tile512_adaptive", | |
| grid_search_triton_gptq_base_p_b_tile512_adaptive, | |
| ), | |
| # ("triton_gptq_base_p_b_tile640", grid_search_triton_gptq_base_p_b_tile640), | |
| # ("triton_gptq_base_p_b_tile768", grid_search_triton_gptq_base_p_b_tile768), | |
| # ("triton_gptq_base_p_b_tile896", grid_search_triton_gptq_base_p_b_tile896), | |
| ("triton_gptq_base_p_b_tile1024", grid_search_triton_gptq_base_p_b_tile1024), | |
| # ("triton_hierarchical", grid_search_triton_hierarchical), | |
| # ("triton_hierarchical_fast", grid_search_triton_hierarchical_fast), | |
| # ("triton_hierarchical_serial", grid_search_triton_hierarchical_serial), | |
| # ("triton_hierarchical_tiled", grid_search_triton_hierarchical_tiled), | |
| # ("triton_cutoff", grid_search_triton_cutoff), | |
| # ("triton_mindist", grid_search_triton_mindist), | |
| # ("triton_multigrp", grid_search_triton_multigroup), | |
| # ("triton_incr2", grid_search_triton_incr2), | |
| # ("triton_incr3", grid_search_triton_incr3), | |
| # ("triton_incrNp1", grid_search_triton_incrNp1), | |
| # ("triton_incrNp2", grid_search_triton_incrNp2), | |
| # ("triton_incrNp3", grid_search_triton_incrNp3), | |
| # ("triton_incrNp4", grid_search_triton_incrNp4), | |
| ] | |
| if args.variants is not None: | |
| requested = set(args.variants) | |
| known = {name for name, _ in variants} | |
| if unknown := requested - known: | |
| raise ValueError(f"Unknown variants: {sorted(unknown)}") | |
| variants = [(name, fn) for name, fn in variants if name in requested] | |
| results = {} | |
| for name, fn in variants: | |
| print(f"Running {name} ...") | |
| times, peak_mb = time_fn(fn, inputs, args.warmup, args.iters) | |
| results[name] = {"times": times, "peak_mb": peak_mb} | |
| medians = {n: sorted(r["times"])[len(r["times"]) // 2] for n, r in results.items()} | |
| name_width = max(16, *(len(name) for name in results)) | |
| print() | |
| print( | |
| f"{'':>{name_width}} {'median':>10} {'min':>10} " | |
| f"{'max':>10} {'peak_mem':>10}" | |
| ) | |
| for name in results: | |
| t = results[name]["times"] | |
| med = medians[name] | |
| peak = results[name]["peak_mb"] | |
| mem_str = f"{peak:.1f}MB" if peak > 0 else "n/a" | |
| print( | |
| f"{name:>{name_width}} {med:>10.4f}s " | |
| f"{min(t):>10.4f}s {max(t):>10.4f}s " | |
| f"{mem_str:>10}" | |
| ) | |
| baseline = "eager_no_patience" | |
| if len(results) > 1 and baseline in medians: | |
| print() | |
| base = medians[baseline] | |
| for name in results: | |
| if name == baseline: | |
| continue | |
| if medians[name] > 0: | |
| print(f"Speedup {name} vs {baseline}: " f"{base / medians[name]:.2f}x") | |
| if __name__ == "__main__": | |
| with torch.no_grad(): | |
| main() |
HDCharles
commented
Sep 14, 2026
Author
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment