-
Notifications
You must be signed in to change notification settings - Fork 5
Expand file tree
/
Copy pathtest_gguf_liger_loss.py
More file actions
217 lines (195 loc) · 6.77 KB
/
Copy pathtest_gguf_liger_loss.py
File metadata and controls
217 lines (195 loc) · 6.77 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
import math
import os
from collections import defaultdict
from pathlib import Path
import gguf
import numpy as np
import pytest
import torch
from liger_kernel.transformers.model.loss_utils import (
LigerForCausalLMLoss,
unpack_cross_entropy_result,
)
from torch.utils._python_dispatch import TorchDispatchMode
from transformers.integrations.gguf import GGUFLinear
from transformers.integrations.gguf_dequant import GGUFQuantizedTensor
from gguf_liger_loss import (
_PACKED_LM_HEAD_CHUNK_SIZE,
_packed_q8_liger_for_causal_lm_loss,
)
_MODEL = Path(
os.environ.get(
"GGUF_MMQ_TEST_MODEL",
os.path.expanduser("~/models/qwen3.6/Qwen3.6-35B-A3B-APEX-I-Mini.gguf"),
)
)
def _require_tensor(value: torch.Tensor | None) -> torch.Tensor:
if value is None:
raise AssertionError("expected a tensor")
return value
class _MMQCounter(TorchDispatchMode):
def __init__(self):
super().__init__()
self.counts = defaultdict(int)
def __torch_dispatch__(self, func, types, args=(), kwargs=None):
name = str(func)
if name.startswith("torch_ggml_ops.mmq"):
self.counts[name] += 1
return func(*args, **(kwargs or {}))
@pytest.fixture(scope="module")
def q6_lm_head() -> GGUFLinear:
if not _MODEL.is_file():
pytest.skip("GGUF model is unavailable")
reader = gguf.GGUFReader(_MODEL)
tensor = next(tensor for tensor in reader.tensors if tensor.name == "output.weight")
out_features = 37
packed_host = np.array(
tensor.data[:out_features], dtype=np.uint8, copy=True, order="C"
)
packed = torch.from_numpy(packed_host).to("cuda")
module = GGUFLinear(
2048,
out_features,
bias=False,
device="cuda",
dtype=torch.bfloat16,
compute_dtype=torch.bfloat16,
)
module.weight = GGUFQuantizedTensor(
packed,
quant_type=tensor.tensor_type,
logical_shape=(out_features, 2048),
)
return module
def test_packed_q8_liger_loss_matches_logical_reference_and_uses_native_ops(
q6_lm_head: GGUFLinear,
monkeypatch: pytest.MonkeyPatch,
) -> None:
generator = torch.Generator(device="cuda").manual_seed(12345)
rows = _PACKED_LM_HEAD_CHUNK_SIZE + 1
hidden_reference = torch.randn(
1,
rows,
2048,
generator=generator,
device="cuda",
dtype=torch.bfloat16,
requires_grad=True,
)
hidden_packed = hidden_reference.detach().clone().requires_grad_(True)
labels = torch.randint(
0,
q6_lm_head.out_features,
(1, rows),
generator=generator,
device="cuda",
)
labels[0, 11] = -100
logical_weight = q6_lm_head.materialize_logical_weight(
dtype=torch.bfloat16, device="cuda"
)
reference_result = LigerForCausalLMLoss(
hidden_states=hidden_reference,
lm_head_weight=logical_weight,
labels=labels,
hidden_size=2048,
return_token_accuracy=True,
return_predicted_tokens=True,
)
reference_loss, _, reference_accuracy, reference_predictions = (
unpack_cross_entropy_result(reference_result)
)
reference_loss.backward()
monkeypatch.setattr(
q6_lm_head,
"materialize_logical_weight",
lambda **kwargs: (_ for _ in ()).throw(
RuntimeError("logical LM-head materialization is forbidden")
),
)
counter = _MMQCounter()
with counter:
packed_result = _packed_q8_liger_for_causal_lm_loss(
hidden_states=hidden_packed,
lm_head=q6_lm_head,
labels=labels,
hidden_size=2048,
return_token_accuracy=True,
return_predicted_tokens=True,
)
packed_loss, _, packed_accuracy, packed_predictions = (
unpack_cross_entropy_result(packed_result)
)
packed_loss.backward()
loss_relative_error = float(
((packed_loss - reference_loss).abs() / reference_loss.abs()).detach()
)
reference_gradient = _require_tensor(hidden_reference.grad).float()
packed_gradient = _require_tensor(hidden_packed.grad).float()
gradient_cosine = float(
torch.nn.functional.cosine_similarity(
reference_gradient.flatten(), packed_gradient.flatten(), dim=0
)
)
gradient_relative_l2 = float(
torch.linalg.vector_norm(packed_gradient - reference_gradient)
/ torch.linalg.vector_norm(reference_gradient)
)
assert loss_relative_error < 5e-3
assert gradient_cosine > 0.999
assert gradient_relative_l2 < 0.03
packed_accuracy = _require_tensor(packed_accuracy)
reference_accuracy = _require_tensor(reference_accuracy)
packed_predictions = _require_tensor(packed_predictions)
reference_predictions = _require_tensor(reference_predictions)
assert packed_accuracy.shape == reference_accuracy.shape == torch.Size([])
assert packed_predictions.shape == reference_predictions.shape == (rows,)
assert torch.isfinite(packed_loss)
assert torch.isfinite(_require_tensor(hidden_packed.grad)).all()
assert q6_lm_head.weight.grad is None
assert _PACKED_LM_HEAD_CHUNK_SIZE == 256
expected_calls = math.ceil(rows / _PACKED_LM_HEAD_CHUNK_SIZE)
assert counter.counts["torch_ggml_ops.mmq.default"] == expected_calls
assert counter.counts["torch_ggml_ops.mmq_grad_input.default"] == expected_calls
@pytest.mark.parametrize(
("loss_kwargs", "message"),
(
({"ce_weight": torch.ones(37)}, "class weights"),
({"label_smoothing": 0.1}, "label smoothing"),
({"use_token_scaling": True}, "token scaling"),
({"final_logit_softcapping": 30.0}, "logit softcapping"),
),
)
def test_packed_q8_liger_loss_rejects_unsupported_objectives(
q6_lm_head: GGUFLinear,
loss_kwargs: dict,
message: str,
) -> None:
hidden = torch.randn(1, 2, 2048, device="cuda", dtype=torch.bfloat16)
labels = torch.tensor([[3, 5]], device="cuda")
with pytest.raises(RuntimeError, match=message):
_packed_q8_liger_for_causal_lm_loss(
hidden_states=hidden,
lm_head=q6_lm_head,
labels=labels,
hidden_size=2048,
**loss_kwargs,
)
def test_packed_q8_liger_loss_rejects_higher_order_gradients(
q6_lm_head: GGUFLinear,
) -> None:
hidden = torch.randn(
1, 2, 2048, device="cuda", dtype=torch.bfloat16, requires_grad=True
)
labels = torch.tensor([[3, 5]], device="cuda")
loss = _packed_q8_liger_for_causal_lm_loss(
hidden_states=hidden,
lm_head=q6_lm_head,
labels=labels,
hidden_size=2048,
)
with pytest.raises(
RuntimeError,
match="Packed Q8_1 GGUF LM-head loss does not support higher-order gradients",
):
torch.autograd.grad(loss, hidden, create_graph=True)