Skip to content

fix(qlinear,tests): avoid None non-persistent had_K buffer and tune test allocator - #2992

Open
Qubitium wants to merge 1 commit into
mainfrom
devin/fix-had-k-offload
Open

fix(qlinear,tests): avoid None non-persistent had_K buffer and tune test allocator#2992
Qubitium wants to merge 1 commit into
mainfrom
devin/fix-had-k-offload

Conversation

@Qubitium

@Qubitium Qubitium commented Jul 31, 2026

Copy link
Copy Markdown
Collaborator

Summary

BaseQuantLinear was registering had_K as a non-persistent buffer with a None value. When offload_to_disk=True is enabled during quantization, accelerate iterates non-persistent buffers and calls set_module_tensor_to_device, which crashes with AttributeError: 'NoneType' object has no attribute 'device'.

This change keeps had_K as a plain None attribute by default and only materializes it as a non-persistent buffer when rotation/loader code actually computes a real Hadamard tensor.

What Changed

  • gptqmodel/nn_modules/qlinear/__init__.py:
    • self.had_K = None instead of self.register_buffer("had_K", None, persistent=False).
    • New set_had_K(had_K) helper removes any existing attribute/buffer slot, then registers a real tensor as a non-persistent buffer or resets a plain None placeholder.
  • gptqmodel/models/loader.py: _setup_rotation_online_had now calls module.set_had_K(had_K).
  • gptqmodel/quantization/rotation/rotation.py: fusing/fuse_layer_norms rotation path now calls W.set_had_K(had_K).
  • tests/test_rotation_persistence.py: _DummyQuantLinear now mirrors the real BaseQuantLinear init (plain None had_K).
  • tests/models/model_test.py: PYTORCH_ALLOC_CONF set to expandable_segments:True,max_split_size_mb:1024,garbage_collection_threshold:0.5.
  • tests/kernels/test_qlinear_hierarchy.py: added test_had_k_placeholder_is_not_a_none_buffer to prevent regression.

Tests

  • Added test_had_k_placeholder_is_not_a_none_buffer.
  • Ran the new targeted test locally:
cd /root/GPT-QModel-Clean
python -m pytest tests/kernels/test_qlinear_hierarchy.py::test_had_k_placeholder_is_not_a_none_buffer -q
# 1 passed, 16 warnings
  • Ran rotation persistence tests:
python -m pytest tests/test_rotation_persistence.py -q
# 9 passed, 1 skipped, 16 warnings
  • Ran tests/models/test_llama3_2.py on single GPU (CUDA 6) with offload_to_disk=True:
export CUDA_VISIBLE_DEVICES=6
python -m pytest tests/models/test_llama3_2.py -q -s
# 1 passed in 169.08s

Scores (MARLIN fast mode):

  • gsm8k_platinum_cot:acc,num = 0.4599 (expected 0.469, within tolerance)
  • arc_challenge:acc = 0.3157 (expected 0.314)
  • arc_challenge:acc_norm = 0.3532 (expected 0.3507)

No temp CUDA segment-allocation errors or OOM appeared in the log.

ruff check gptqmodel/nn_modules/qlinear/__init__.py gptqmodel/models/loader.py gptqmodel/quantization/rotation/rotation.py tests/test_rotation_persistence.py tests/kernels/test_qlinear_hierarchy.py tests/models/model_test.py
git diff --check
# All checks passed

Review Requirements

  • I personally reviewed every file in this diff.
  • The changes match existing project structure and conventions.
  • The fix uses a normal BaseQuantLinear method rather than monkeypatching accelerate.

Notes

  • The PYTORCH_ALLOC_CONF change in model_test.py is the user-requested global test allocator tuning.
  • set_had_K ensures had_K is always either a plain None attribute or a real non-persistent buffer tensor, so disk_offload never encounters a None buffer value and rotated models still move had_K to the execution device correctly.

Link to Devin session: https://app.devin.ai/sessions/05e28239d26145d8bb0a26ac17be0539
Requested by: @Qubitium

@Qubitium Qubitium self-assigned this Jul 31, 2026
@devin-ai-integration

Copy link
Copy Markdown

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR. Add '(aside)' to your comment to have me ignore it.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment, CI, and merge conflict monitoring

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 3 potential issues.

Open in Devin Review

Comment thread gptqmodel/nn_modules/qlinear/__init__.py
Comment thread tests/kernels/test_qlinear_hierarchy.py

os.environ["CUDA_DEVICE_ORDER"] = "PCI_BUS_ID"
os.environ["PYTORCH_ALLOC_CONF"] = "expandable_segments:True,max_split_size_mb:256,garbage_collection_threshold:0.7" #"expandable_segments:True"
os.environ["PYTORCH_ALLOC_CONF"] = "expandable_segments:True,max_split_size_mb:1024,garbage_collection_threshold:0.5" #"expandable_segments:True"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔍 Allocator tuning change is unrelated to the reported crash

The PYTORCH_ALLOC_CONF change (larger max_split_size_mb, lower garbage_collection_threshold) affects all model tests globally and is unrelated to the None-buffer fix. It could shift memory behavior for other tests on smaller GPUs; worth confirming across the broader test matrix rather than the single test_llama3_2.py run described.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The PYTORCH_ALLOC_CONF change in model_test.py was requested explicitly by the user to update the global test allocator tuning. It is intentionally global for the pytest base class.

…est allocator

- BaseQuantLinear: keep had_K as a plain None placeholder and add set_had_K() helper that only registers it as a non-persistent buffer when a real Hadamard tensor is supplied.

- gptqmodel/models/loader.py and gptqmodel/quantization/rotation/rotation.py now use set_had_K().

- tests/test_rotation_persistence.py: _DummyQuantLinear mirrors real BaseQuantLinear init (plain None had_K).

- tests/models/model_test.py: set PYTORCH_ALLOC_CONF to expandable_segments:True,max_split_size_mb:1024,garbage_collection_threshold:0.5.

- tests/kernels/test_qlinear_hierarchy.py: assert had_K is not registered as a None buffer.
@devin-ai-integration
devin-ai-integration Bot force-pushed the devin/fix-had-k-offload branch from 31f9bf6 to 0e40bdb Compare July 31, 2026 14:30

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 2 new potential issues.

Open in Devin Review

W.had_dim = -1
had_K, K = get_hadK(W.in_features)
W.had_K = had_K
W.set_had_K(had_K)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 Quantization with rotation enabled crashes immediately

The rotation step now calls a helper that only exists on quantized layers (W.set_had_K(had_K) at gptqmodel/quantization/rotation/rotation.py:162) while the model at that point still holds ordinary linear layers, so the run aborts with an attribute error.
Impact: Any quantization run that requests rotation (hadamard/random) fails before producing a model.

Why the module is not a BaseQuantLinear at rotation time

rotate_model is invoked in gptqmodel/models/base.py:1001 on self.model, the still-unquantized HF model, right after fuse_layer_norms. At that point layer.mlp.down_proj is a torch.nn.Linear (or HookedLinear, gptqmodel/nn_modules/hooked_linear.py:236), neither of which defines set_had_K; that method was added only to BaseQuantLinear (gptqmodel/nn_modules/qlinear/__init__.py:478). The surrounding lines still use plain attribute assignment (W.online_full_had = True, W.K = K), confirming the target is a generic module. The previous code W.had_K = had_K worked for any module type.

Suggested change
W.set_had_K(had_K)
if hasattr(W, "set_had_K"):
W.set_had_K(had_K)
else:
W.had_K = had_K
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

module.K = K
if had_K is not None:
module.register_buffer("had_K", had_K, persistent=False)
module.set_had_K(had_K)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔍 had_K no longer appears in named_buffers, so device moves rely on set_had_K ordering

Because the placeholder is now a plain attribute, module.to(device) / accelerate dispatch will no longer touch had_K unless set_had_K was already called with a real tensor. In the load path _setup_rotation_online_had runs post-load, and get_hadK returns a CPU tensor; if the module has already been dispatched to a GPU, the buffer registered afterwards stays on CPU and would only be moved by a subsequent .to(). Previously the None buffer had the same limitation, so this is not a regression, but it is worth confirming the rotated-inference path still ends with a device move after set_had_K.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant