Hacktoberfest 2026: những issue maintainer đã đánh dấu cho tháng Mười, đang mở và phù hợp người mới. Xem issue Hacktoberfest

GGML fake-quant keeps stale codes after a bitsandbytes optimizer step, so QAT/QAD silently stops training the quantized weights

Đang mở
#2,857 0 bình luận 0 reaction 0 người được giao Xem trên GitHub

Maintainer thường phản hồi trong vòng 2 ngày

@dcaayushd đang làm issue này rồi.

Từ ngày 19/10/2026.

  • #2874 của @dcaayushd — đang mở

Đánh giá

Độ khó
4/5
Thời gian dự kiến
3-5 ngày
Mức phù hợp với người mới
54/100
Loại issue
Lỗi
Độ rõ ràng
Đặc tả rõ ràng
Mức độ hoạt động
Sôi nổi
Công nghệ
python, pytorch
Lĩnh vực
machine-learning

Hướng nghiên cứu

Start in modelopt/torch/quantization/ggml/common.py and trace how _input_cache_key decides whether to reuse the encoded payload. Run the provided reproduction with both AdamW and AdamW8bit to confirm the stale-cache behavior. Done means the forward after either optimizer step uses codes encoded from the updated weights.

Do mô hình lập chỉ mục viết ra từ nội dung của issue.

Mô tả

Describe the bug

The GGML backends cache the encoded payload and reuse it while _input_cache_key is unchanged (modelopt/torch/quantization/ggml/common.py). The key includes the weight's PyTorch version counter (inputs._version). bitsandbytes' 8-bit optimizers update parameters from their own CUDA kernels, which doesn't bump that counter. So after optimizer.step(), the next forward reuses the payload encoded before the step: the fp32 weights move, but the quantized weights the model computes with never change.

Impact: QAT/QAD with a GGML format and a bitsandbytes optimizer (for example transformers' optim="adamw_bnb_8bit", a common choice on smaller GPUs) silently trains only the unquantized layers. Nothing errors, and the loss still goes down because those layers keep training, so it's easy to miss. I tested iq2_xxs; the cache is shared by the GGML formats, so the others should behave the same.

I hit this distilling an IQ2_XXS Qwen3-1.7B (general/ptq/iq2_xxs, MLP only): after 12 steps at lr 1e-4, 100% of the MLP weights had moved and 0% of the codes the forward used had changed.

Steps/Code to reproduce bug
import torch
import modelopt.torch.quantization as mtq

IQ2 = {"num_bits": "iq2_xxs", "block_sizes": {-1: 256}, "backend": "ggml"}

def run(make_opt):
    torch.manual_seed(0)
    model = torch.nn.Sequential(torch.nn.Linear(512, 512, bias=False)).cuda()
    cfg = {"quant_cfg": [{"quantizer_name": "*", "enable": False},
                         {"quantizer_name": "*weight_quantizer", "cfg": IQ2}],
           "algorithm": "max"}
    x = torch.randn(64, 512, device="cuda")
    model = mtq.quantize(model, cfg, lambda m: m(x))
    lin = model[0]
    q = lin.weight_quantizer
    opt = make_opt(lin.weight)
    model(x).square().mean().backward()                  # forward encodes and caches the payload
    before = q._quantizer_cache.packed_weights.clone()
    v0 = lin.weight._version
    opt.step()
    with torch.no_grad():
        model(x)                                         # the next forward
        used = q._quantizer_cache.packed_weights.clone()
        q._quantizer_cache = None
        model(x)
        fresh = q._quantizer_cache.packed_weights.clone()  # a fresh encode of the updated weight
    changed = lambda a: (a != before).any(-1).float().mean().item()
    print(f"{type(opt).__name__}: weight version {v0} -> {lin.weight._version}; blocks changed: "
          f"next forward {changed(used):.1%}, fresh encode {changed(fresh):.1%}")

import bitsandbytes as bnb
run(lambda p: torch.optim.AdamW([p], lr=1e-2))
run(lambda p: bnb.optim.AdamW8bit([p], lr=1e-2))

Output:

AdamW: weight version 1 -> 3; blocks changed: next forward 100.0%, fresh encode 100.0%
AdamW8bit: weight version 1 -> 1; blocks changed: next forward 0.0%, fresh encode 100.0%
Expected behavior

The forward after an optimizer step uses the codes of the updated weight, whichever optimizer made the update.

My workaround is setting weight_quantizer._quantizer_cache = None on every quantized module after each step. Possible fixes: clear the cache from an optimizer step hook in the QAT/QAD trainers, or have the cache check notice changes that bypass the version counter.

Who can help?
  • @cjluo-nv (GGML formats)

System information

  • Container used (if applicable): none
  • OS: Ubuntu 24.04.4 LTS
  • CPU architecture: x86_64
  • GPU name: Tesla V100-PCIE-32GB
  • GPU memory size: 32 GB
  • Number of GPUs: 1
  • Library versions:
    • Python: 3.12.3
    • ModelOpt version or commit hash: 0.48.0rc0 (the cache key is unchanged on main at 0df6ab3a2d)
    • CUDA: 12.6 (PyTorch build), driver 580.173.02
    • PyTorch: 2.8.0+cu126
    • Transformers: 5.18.0
    • bitsandbytes: 0.50.2
    • TensorRT-LLM / ONNXRuntime / TensorRT: not used
  • Any other details that may help: Found while reproducing the Nemotron 3.5 Bolt 2-bit recipe at small scale on a V100 (the GGML CUDA extension builds for sm_70 and its IQ2_XXS GPU tests pass). I worked on this with an AI assistant (Claude); the reproduction above was run and checked on my machine.
Ngôn ngữ chính
Python
Star
5.4k
Fork
767
Merge trung bình
3 ngày 11 giờ
Pull request đã merge (30 ngày)
203

Chuẩn bị môi trường

Bắt đầu từ đâu

  1. Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
  2. Bình luận trên issue rằng bạn sẽ nhận — tránh hai người làm cùng một việc.
  3. Fork repository và làm thay đổi trên một nhánh.
  4. Mở pull request có tham chiếu số hiệu của issue.

Issue khác của NVIDIA/Model-Optimizer

Tất cả issue của NVIDIA/Model-Optimizer

Issue tương tự

Thêm issue về Python

Nhận issue mới trong hộp thư của bạn

Bản tóm tắt ngắn những issue GitHub phù hợp với người mới.