Skip to content

Commit b9940cc

Browse files
feat(global_ptq): wire MDBF QAT into the KL distillation loop
- helpers.detect_quantization_method: recognize MultipathMDBFLinear layers as method "mdbf" (priority gptq > dbf > mdbf). Without this a MDBF-quantized model was reported as having no quantized layers and the distillation loop skipped it entirely. - core.run_kl_distillation: add the "mdbf" branch (setup/teardown via mdbf_adapter, param groups for amplitude/binary params using dbf_lr) and expose mdbf_ste_k for the sign STE sharpness. - core.eval_kl / trainer._GlobalPTQTrainer.compute_loss: support a separate teacher_device so the FP16 teacher can be kept on CPU or a second GPU while the student trains under DeepSpeed ZeRO-2 with CPU optimizer offload. Required to fit 1bpw Llama2-7B/13B QAT on 80GB cards. - global_ptq.py / global_ptq_distributed.py: expose the corresponding GlobalPTQConfig(Distributed) fields (mdbf_ste_k, student_device, teacher_device) and thread them through to run_kl_distillation and the Trainer-based distributed path. Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent b8569af commit b9940cc

5 files changed

Lines changed: 223 additions & 35 deletions

File tree

global_ptq/onecomp_globalptq/global_ptq/_core/core.py

Lines changed: 97 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,15 @@
5454
write_back_dbf_binary,
5555
write_back_dbf_scaling,
5656
)
57+
from .mdbf_adapter import (
58+
load_mdbf_state,
59+
restore_mdbf_original,
60+
save_mdbf_state,
61+
setup_mdbf_differentiable,
62+
setup_mdbf_forwards_only,
63+
write_back_mdbf_amp,
64+
write_back_mdbf_binary,
65+
)
5766

5867
logger = getLogger(__name__)
5968

@@ -418,17 +427,33 @@ def cosine_warmup_lr_lambda(
418427
# ---------------------------------------------------------------------------
419428

420429

430+
@torch.no_grad()
431+
def _teacher_logits(
432+
teacher_model: nn.Module,
433+
input_ids: torch.Tensor,
434+
teacher_dev: torch.device,
435+
student_dev: torch.device,
436+
) -> torch.Tensor:
437+
"""Run teacher forward; move logits to *student_dev* if devices differ."""
438+
if teacher_dev == student_dev:
439+
return get_logits(teacher_model(input_ids))
440+
logits_t = get_logits(teacher_model(input_ids.to(teacher_dev)))
441+
return logits_t.to(student_dev)
442+
443+
421444
@torch.no_grad()
422445
def eval_kl(
423446
model: nn.Module,
424447
teacher_model: nn.Module,
425448
dataloader: List[Dict[str, torch.Tensor]],
426449
dev: torch.device,
427450
temperature: float = 1.0,
451+
teacher_dev: Optional[torch.device] = None,
428452
) -> float:
429453
"""Mean KL divergence over *dataloader* batches."""
430454
was_training = model.training
431455
model.eval()
456+
teacher_dev = teacher_dev or dev
432457
total, n = 0.0, 0
433458
for batch in dataloader:
434459
input_ids = batch["input_ids"].to(dev)
@@ -437,7 +462,7 @@ def eval_kl(
437462
attention_mask = attention_mask.to(dev)
438463

439464
logits_s = get_logits(model(input_ids))
440-
logits_t = get_logits(teacher_model(input_ids))
465+
logits_t = _teacher_logits(teacher_model, input_ids, teacher_dev, dev)
441466
total += compute_kl_loss(
442467
logits_t, logits_s, temperature, attention_mask=attention_mask,
443468
).item()
@@ -587,6 +612,7 @@ def run_kl_distillation(
587612
gptq_intweight_lr: float = 1e-4,
588613
optimize_binary: bool = False,
589614
ste_k: float = 100.0,
615+
mdbf_ste_k: float = 2.0,
590616
calibration_dataset=None,
591617
num_calibration_samples: int = 128,
592618
max_length: int = 2048,
@@ -616,12 +642,17 @@ def run_kl_distillation(
616642
early_stopping_patience: int = 0,
617643
use_mixed_precision: bool = False,
618644
grad_accum_steps: int = 1,
645+
student_device: Optional[str] = None,
646+
teacher_device: Optional[str] = None,
619647
) -> Dict:
620648
"""Run KL-distillation global PTQ on a GPTQ or DBF quantized model.
621649
622650
The model is modified **in-place**. Returns a results dict.
623651
"""
624-
dev = torch.device("cuda" if torch.cuda.is_available() else "cpu")
652+
dev = torch.device(
653+
student_device or ("cuda" if torch.cuda.is_available() else "cpu")
654+
)
655+
teacher_dev = torch.device(teacher_device) if teacher_device else dev
625656

626657
# ------------------------------------------------------------------
627658
# 1. Detect method
@@ -631,7 +662,7 @@ def run_kl_distillation(
631662
logger.warning("No quantized layers detected — skipping global PTQ.")
632663
return {"global_executed": False, "reason": "not_quantized"}
633664

634-
if method not in ("gptq", "dbf"):
665+
if method not in ("gptq", "dbf", "mdbf"):
635666
logger.info("Method '%s' detected — not supported.", method)
636667
return {"global_executed": False, "reason": f"unsupported_method_{method}"}
637668

@@ -665,7 +696,8 @@ def run_kl_distillation(
665696
teacher_model.eval()
666697
for p in teacher_model.parameters():
667698
p.requires_grad = False
668-
teacher_model.to(dev)
699+
if teacher_dev.type != "cpu":
700+
teacher_model.to(teacher_dev)
669701

670702
# ------------------------------------------------------------------
671703
# 4. Move student to GPU and set up differentiable parameters
@@ -675,6 +707,7 @@ def run_kl_distillation(
675707

676708
gptq_modules: list = []
677709
dbf_modules: list = []
710+
mdbf_modules: list = []
678711
original_forwards: Dict[str, object] = {}
679712
param_groups: list = []
680713
binary_params: list = []
@@ -710,13 +743,33 @@ def run_kl_distillation(
710743
f", {len(binary_params)} binary" if binary_params else "",
711744
)
712745

746+
elif method == "mdbf":
747+
mdbf_modules = detected_modules
748+
original_forwards, scaling_params, binary_params = setup_mdbf_differentiable(
749+
mdbf_modules, optimize_binary, ste_k=mdbf_ste_k,
750+
)
751+
logger.info("MDBF binary STE sharpness mdbf_ste_k=%.4g", mdbf_ste_k)
752+
all_mdbf_params = list(scaling_params)
753+
if binary_params:
754+
all_mdbf_params += binary_params
755+
param_groups = [{"params": all_mdbf_params, "lr": dbf_lr}]
756+
757+
logger.info(
758+
"Trainable: %d amp params%s across %d MDBF modules",
759+
len(scaling_params),
760+
f", {len(binary_params)} binary" if binary_params else "",
761+
len(mdbf_modules),
762+
)
763+
713764
total_trainable = sum(len(pg["params"]) for pg in param_groups)
714765
if total_trainable == 0:
715766
logger.warning("No trainable parameters — skipping.")
716767
if method == "gptq":
717768
restore_gptq_original(gptq_modules, original_forwards)
718769
elif method == "dbf":
719770
restore_dbf_original(dbf_modules, original_forwards)
771+
elif method == "mdbf":
772+
restore_mdbf_original(mdbf_modules, original_forwards)
720773
quantized_model.cpu()
721774
del teacher_model
722775
gc.collect()
@@ -871,17 +924,24 @@ def run_kl_distillation(
871924
if method == "gptq":
872925
initial_state = save_gptq_state(gptq_modules)
873926
restore_gptq_original(gptq_modules, original_forwards)
874-
else:
927+
elif method == "dbf":
875928
initial_state = save_dbf_state(dbf_modules)
876929
restore_dbf_original(dbf_modules, original_forwards)
930+
else: # mdbf
931+
initial_state = save_mdbf_state(mdbf_modules)
932+
restore_mdbf_original(mdbf_modules, original_forwards)
877933

878-
initial_kl = eval_kl(quantized_model, teacher_model, dataloader, dev, temperature)
934+
initial_kl = eval_kl(
935+
quantized_model, teacher_model, dataloader, dev, temperature, teacher_dev,
936+
)
879937
logger.info("Initial KL = %.6f", initial_kl)
880938

881939
if method == "gptq":
882940
setup_gptq_forwards_only(gptq_modules, original_forwards, gptq_optimize_intweight)
883941
elif method == "dbf":
884942
setup_dbf_forwards_only(dbf_modules, original_forwards)
943+
elif method == "mdbf":
944+
setup_mdbf_forwards_only(mdbf_modules, original_forwards)
885945

886946
# ------------------------------------------------------------------
887947
# 7. Training loop
@@ -928,8 +988,9 @@ def _forward_and_loss() -> Tuple[torch.Tensor, torch.Tensor, torch.Tensor]:
928988

929989
with amp_ctx:
930990
logits_s = get_logits(quantized_model(input_ids))
931-
with torch.no_grad():
932-
logits_t = get_logits(teacher_model(input_ids))
991+
logits_t = _teacher_logits(
992+
teacher_model, input_ids, teacher_dev, dev,
993+
)
933994

934995
kl = compute_kl_loss(
935996
logits_t, logits_s, temperature, attention_mask=attention_mask,
@@ -1052,23 +1113,33 @@ def _forward_and_loss() -> Tuple[torch.Tensor, torch.Tensor, torch.Tensor]:
10521113
elif method == "dbf":
10531114
write_back_dbf_binary(dbf_modules)
10541115
restore_dbf_original(dbf_modules, original_forwards)
1116+
elif method == "mdbf":
1117+
write_back_mdbf_binary(mdbf_modules)
1118+
write_back_mdbf_amp(mdbf_modules)
1119+
restore_mdbf_original(mdbf_modules, original_forwards)
10551120

1056-
current_kl = eval_kl(quantized_model, teacher_model, dataloader, dev, temperature)
1121+
current_kl = eval_kl(
1122+
quantized_model, teacher_model, dataloader, dev, temperature, teacher_dev,
1123+
)
10571124

10581125
if current_kl < best_kl:
10591126
best_kl = current_kl
10601127
patience_counter = 0
10611128
if method == "gptq":
10621129
best_state = save_gptq_state(gptq_modules)
1063-
else:
1130+
elif method == "dbf":
10641131
best_state = save_dbf_state(dbf_modules)
1132+
else: # mdbf
1133+
best_state = save_mdbf_state(mdbf_modules)
10651134
else:
10661135
patience_counter += 1
10671136

10681137
if method == "gptq":
10691138
setup_gptq_forwards_only(gptq_modules, original_forwards, gptq_optimize_intweight)
10701139
elif method == "dbf":
10711140
setup_dbf_forwards_only(dbf_modules, original_forwards)
1141+
elif method == "mdbf":
1142+
setup_mdbf_forwards_only(mdbf_modules, original_forwards)
10721143

10731144
# Restore non-EMA params for continued training
10741145
if ema_tracker is not None:
@@ -1103,27 +1174,36 @@ def _forward_and_loss() -> Tuple[torch.Tensor, torch.Tensor, torch.Tensor]:
11031174
if best_state is not None and best_kl < initial_kl:
11041175
if method == "gptq":
11051176
load_gptq_state(gptq_modules, best_state)
1106-
else:
1177+
elif method == "dbf":
11071178
load_dbf_state(dbf_modules, best_state)
1179+
else: # mdbf
1180+
load_mdbf_state(mdbf_modules, best_state)
11081181
logger.info("Loaded best state (KL=%.6f)", best_kl)
11091182
elif best_kl >= initial_kl:
11101183
logger.info("No improvement — rolling back to initial state.")
11111184
if method == "gptq":
11121185
load_gptq_state(gptq_modules, initial_state)
1113-
else:
1186+
elif method == "dbf":
11141187
load_dbf_state(dbf_modules, initial_state)
1188+
else: # mdbf
1189+
load_mdbf_state(mdbf_modules, initial_state)
11151190
best_kl = initial_kl
11161191
else:
11171192
if method == "gptq":
11181193
write_back_gptq_params(gptq_modules, gptq_optimize_intweight)
11191194
elif method == "dbf":
11201195
write_back_dbf_binary(dbf_modules)
11211196
write_back_dbf_scaling(dbf_modules)
1197+
elif method == "mdbf":
1198+
write_back_mdbf_binary(mdbf_modules)
1199+
write_back_mdbf_amp(mdbf_modules)
11221200

11231201
if method == "gptq":
11241202
restore_gptq_original(gptq_modules, original_forwards, cleanup=False)
11251203
elif method == "dbf":
11261204
restore_dbf_original(dbf_modules, original_forwards, cleanup=False)
1205+
elif method == "mdbf":
1206+
restore_mdbf_original(mdbf_modules, original_forwards, cleanup=False)
11271207

11281208
# Cleanup hooks
11291209
if use_inter_loss:
@@ -1140,14 +1220,18 @@ def _forward_and_loss() -> Tuple[torch.Tensor, torch.Tensor, torch.Tensor]:
11401220

11411221
# Final evaluation
11421222
quantized_model.eval()
1143-
final_kl = eval_kl(quantized_model, teacher_model, dataloader, dev, temperature)
1223+
final_kl = eval_kl(
1224+
quantized_model, teacher_model, dataloader, dev, temperature, teacher_dev,
1225+
)
11441226

11451227
# Cleanup
11461228
if method == "gptq":
11471229
# Final cleanup of differentiable parameters
11481230
restore_gptq_original(gptq_modules, original_forwards, cleanup=True)
11491231
elif method == "dbf":
11501232
restore_dbf_original(dbf_modules, original_forwards, cleanup=True)
1233+
elif method == "mdbf":
1234+
restore_mdbf_original(mdbf_modules, original_forwards, cleanup=True)
11511235

11521236
del teacher_model
11531237
gc.collect()

global_ptq/onecomp_globalptq/global_ptq/_core/helpers.py

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -76,30 +76,34 @@ def detect_quantization_method(
7676
"""Auto-detect the quantization method applied to *model*.
7777
7878
Returns:
79-
(method, modules) where *method* is ``"gptq"``, ``"dbf"``, or
80-
``None``, and *modules* is the list of ``(name, module)`` pairs
81-
for the detected quantized layers.
79+
(method, modules) where *method* is ``"gptq"``, ``"dbf"``,
80+
``"mdbf"``, or ``None``, and *modules* is the list of
81+
``(name, module)`` pairs for the detected quantized layers.
8282
83-
When both GPTQ and DBF layers are present (mixed quantization),
84-
a warning is emitted and only GPTQ layers are returned.
83+
Priority: GPTQ > DBF > MDBF. When multiple types coexist a warning is
84+
emitted and only the highest-priority layers are returned.
8585
"""
8686
from onecomp.quantizer.gptq.gptq_layer import GPTQLinear
8787
from onecomp.quantizer.dbf.dbf_layer import DoubleBinaryLinear
88+
from onecomp.quantizer.mdbf.mdbf_layer import MultipathMDBFLinear
8889

8990
gptq_modules = find_target_modules(model, GPTQLinear)
9091
dbf_modules = find_target_modules(model, DoubleBinaryLinear)
92+
mdbf_modules = find_target_modules(model, MultipathMDBFLinear)
9193

92-
if gptq_modules and dbf_modules:
94+
if gptq_modules and (dbf_modules or mdbf_modules):
9395
logger.warning(
94-
"Mixed GPTQ + DBF model detected (gptq=%d, dbf=%d). "
96+
"Mixed GPTQ + DBF/MDBF model detected (gptq=%d, dbf=%d, mdbf=%d). "
9597
"Global PTQ currently optimises GPTQ layers only; "
96-
"DBF layers will be skipped.",
97-
len(gptq_modules), len(dbf_modules),
98+
"other layers will be skipped.",
99+
len(gptq_modules), len(dbf_modules), len(mdbf_modules),
98100
)
99101
if gptq_modules:
100102
return "gptq", gptq_modules
101103
if dbf_modules:
102104
return "dbf", dbf_modules
105+
if mdbf_modules:
106+
return "mdbf", mdbf_modules
103107
return None, []
104108

105109

0 commit comments

Comments
 (0)