polgrad 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- polgrad/__init__.py +93 -0
- polgrad/_validation.py +160 -0
- polgrad/advantages.py +449 -0
- polgrad/aggregate.py +222 -0
- polgrad/conformance/__init__.py +25 -0
- polgrad/conformance/_vendor/__init__.py +1 -0
- polgrad/conformance/_vendor/openrlhf_loss.py +250 -0
- polgrad/conformance/_vendor/verl_core_algos.py +430 -0
- polgrad/conformance/deviations.py +108 -0
- polgrad/conformance/harness.py +498 -0
- polgrad/diagnostics/__init__.py +35 -0
- polgrad/diagnostics/clipping.py +182 -0
- polgrad/diagnostics/entropy.py +236 -0
- polgrad/diagnostics/ess.py +212 -0
- polgrad/diagnostics/length_bias.py +196 -0
- polgrad/diagnostics/mismatch.py +210 -0
- polgrad/kl.py +269 -0
- polgrad/losses.py +591 -0
- polgrad/py.typed +0 -0
- polgrad/registry.py +595 -0
- polgrad/verify/__init__.py +24 -0
- polgrad/verify/goldens.py +360 -0
- polgrad/verify/gradcheck.py +364 -0
- polgrad/verify/mc.py +124 -0
- polgrad-0.1.0.dist-info/METADATA +243 -0
- polgrad-0.1.0.dist-info/RECORD +29 -0
- polgrad-0.1.0.dist-info/WHEEL +4 -0
- polgrad-0.1.0.dist-info/licenses/LICENSE +202 -0
- polgrad-0.1.0.dist-info/licenses/NOTICE +42 -0
polgrad/__init__.py
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
"""Reference semantics, conformance testing, and pathology diagnostics for LLM
|
|
2
|
+
policy-gradient post-training.
|
|
3
|
+
|
|
4
|
+
The core namespace exports the loss algebra, KL estimators, advantage estimators, and the
|
|
5
|
+
algorithm registry. ``polgrad.diagnostics``, ``polgrad.verify``, and
|
|
6
|
+
``polgrad.conformance`` are subpackages with their own exports. Conventions (shapes,
|
|
7
|
+
masking, signs, dtypes) are specified in ``docs/conventions.md`` and enforced by tests.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
from polgrad.advantages import (
|
|
13
|
+
GAEConfig,
|
|
14
|
+
GroupNormConfig,
|
|
15
|
+
ReinforcePPConfig,
|
|
16
|
+
broadcast_to_tokens,
|
|
17
|
+
gae,
|
|
18
|
+
grpo_advantages,
|
|
19
|
+
reinforce_pp_advantages,
|
|
20
|
+
rloo_advantages,
|
|
21
|
+
whiten,
|
|
22
|
+
)
|
|
23
|
+
from polgrad.aggregate import (
|
|
24
|
+
Aggregation,
|
|
25
|
+
aggregate,
|
|
26
|
+
effective_token_weights,
|
|
27
|
+
microbatch_token_weights,
|
|
28
|
+
)
|
|
29
|
+
from polgrad.kl import (
|
|
30
|
+
KLEstimator,
|
|
31
|
+
KLLossConfig,
|
|
32
|
+
kl_estimate,
|
|
33
|
+
kl_in_reward,
|
|
34
|
+
kl_loss,
|
|
35
|
+
reverse_kl_grad_surrogate,
|
|
36
|
+
)
|
|
37
|
+
from polgrad.losses import (
|
|
38
|
+
ClipConfig,
|
|
39
|
+
ISCorrectionConfig,
|
|
40
|
+
PolicyLossConfig,
|
|
41
|
+
PolicyLossResult,
|
|
42
|
+
RatioKind,
|
|
43
|
+
SurrogateKind,
|
|
44
|
+
ValueLossResult,
|
|
45
|
+
policy_loss,
|
|
46
|
+
value_loss,
|
|
47
|
+
)
|
|
48
|
+
from polgrad.registry import (
|
|
49
|
+
ALGORITHMS,
|
|
50
|
+
AlgorithmSpec,
|
|
51
|
+
Citation,
|
|
52
|
+
)
|
|
53
|
+
from polgrad.registry import describe as describe_algorithm
|
|
54
|
+
from polgrad.registry import get as get_algorithm
|
|
55
|
+
|
|
56
|
+
__version__ = "0.1.0"
|
|
57
|
+
|
|
58
|
+
__all__ = [
|
|
59
|
+
"ALGORITHMS",
|
|
60
|
+
"Aggregation",
|
|
61
|
+
"AlgorithmSpec",
|
|
62
|
+
"Citation",
|
|
63
|
+
"ClipConfig",
|
|
64
|
+
"GAEConfig",
|
|
65
|
+
"GroupNormConfig",
|
|
66
|
+
"ISCorrectionConfig",
|
|
67
|
+
"KLEstimator",
|
|
68
|
+
"KLLossConfig",
|
|
69
|
+
"PolicyLossConfig",
|
|
70
|
+
"PolicyLossResult",
|
|
71
|
+
"RatioKind",
|
|
72
|
+
"ReinforcePPConfig",
|
|
73
|
+
"SurrogateKind",
|
|
74
|
+
"ValueLossResult",
|
|
75
|
+
"__version__",
|
|
76
|
+
"aggregate",
|
|
77
|
+
"broadcast_to_tokens",
|
|
78
|
+
"describe_algorithm",
|
|
79
|
+
"effective_token_weights",
|
|
80
|
+
"gae",
|
|
81
|
+
"get_algorithm",
|
|
82
|
+
"grpo_advantages",
|
|
83
|
+
"kl_estimate",
|
|
84
|
+
"kl_in_reward",
|
|
85
|
+
"kl_loss",
|
|
86
|
+
"microbatch_token_weights",
|
|
87
|
+
"policy_loss",
|
|
88
|
+
"reinforce_pp_advantages",
|
|
89
|
+
"reverse_kl_grad_surrogate",
|
|
90
|
+
"rloo_advantages",
|
|
91
|
+
"value_loss",
|
|
92
|
+
"whiten",
|
|
93
|
+
]
|
polgrad/_validation.py
ADDED
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
"""Input validation and tiny shared numeric helpers used across polgrad.
|
|
2
|
+
|
|
3
|
+
Single source of error-message style. See ``docs/conventions.md`` for the shape and
|
|
4
|
+
masking rules these helpers enforce. Logprob tensors are validated for finiteness only:
|
|
5
|
+
real frameworks emit slightly positive logprob values from numerics, so rejecting
|
|
6
|
+
``> 0`` would reject real data. The per-site message parameters of
|
|
7
|
+
:func:`broadcast_advantages` exist because callers' error strings are pinned by tests;
|
|
8
|
+
the wording differences are preserved deliberately, not drift.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import torch
|
|
14
|
+
from torch import Tensor
|
|
15
|
+
|
|
16
|
+
__all__ = [
|
|
17
|
+
"broadcast_advantages",
|
|
18
|
+
"check_1d",
|
|
19
|
+
"check_2d",
|
|
20
|
+
"check_finite",
|
|
21
|
+
"check_logprob_streams",
|
|
22
|
+
"check_mask",
|
|
23
|
+
"check_same_shape",
|
|
24
|
+
"std_or_zero",
|
|
25
|
+
]
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def check_1d(name: str, x: Tensor) -> None:
|
|
29
|
+
"""Raise ``ValueError`` unless ``x`` is 1-D ``[B]``."""
|
|
30
|
+
if x.dim() != 1:
|
|
31
|
+
raise ValueError(f"{name} must be 1-D [B]; got shape {tuple(x.shape)}")
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def check_2d(name: str, x: Tensor) -> None:
|
|
35
|
+
"""Raise ``ValueError`` unless ``x`` is 2-D ``[B, T]``."""
|
|
36
|
+
if x.dim() != 2:
|
|
37
|
+
raise ValueError(f"{name} must be 2-D [B, T]; got shape {tuple(x.shape)}")
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def check_same_shape(name_a: str, a: Tensor, name_b: str, b: Tensor) -> None:
|
|
41
|
+
"""Raise ``ValueError`` unless ``a`` and ``b`` have identical shapes."""
|
|
42
|
+
if a.shape != b.shape:
|
|
43
|
+
raise ValueError(
|
|
44
|
+
f"{name_a} and {name_b} must have identical shapes; "
|
|
45
|
+
f"got {tuple(a.shape)} vs {tuple(b.shape)}"
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def check_finite(name: str, x: Tensor) -> None:
|
|
50
|
+
"""Raise ``ValueError`` if ``x`` contains NaN or infinite values."""
|
|
51
|
+
if not bool(torch.isfinite(x).all()):
|
|
52
|
+
raise ValueError(f"{name} contains non-finite values")
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def check_mask(response_mask: Tensor, *, like: Tensor) -> None:
|
|
56
|
+
"""Validate a response mask against a reference tensor.
|
|
57
|
+
|
|
58
|
+
Enforces the masking rules of ``docs/conventions.md``: ``response_mask`` must be a
|
|
59
|
+
2-D boolean tensor with the same shape as ``like``, and every row must contain at
|
|
60
|
+
least one response token.
|
|
61
|
+
|
|
62
|
+
Raises:
|
|
63
|
+
ValueError: On dtype, dimensionality, or shape mismatch, or if any row of the
|
|
64
|
+
mask has zero response tokens.
|
|
65
|
+
"""
|
|
66
|
+
if response_mask.dtype != torch.bool:
|
|
67
|
+
raise ValueError(f"response_mask must have dtype torch.bool; got {response_mask.dtype}")
|
|
68
|
+
check_2d("response_mask", response_mask)
|
|
69
|
+
if response_mask.shape != like.shape:
|
|
70
|
+
raise ValueError(
|
|
71
|
+
f"response_mask shape {tuple(response_mask.shape)} does not match "
|
|
72
|
+
f"input shape {tuple(like.shape)}"
|
|
73
|
+
)
|
|
74
|
+
row_counts = response_mask.sum(dim=1)
|
|
75
|
+
if int(row_counts.min()) == 0:
|
|
76
|
+
empty = torch.nonzero(row_counts == 0).flatten().tolist()
|
|
77
|
+
raise ValueError(f"response_mask has rows with zero response tokens: rows {empty}")
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def check_logprob_streams(
|
|
81
|
+
name_a: str,
|
|
82
|
+
a: Tensor,
|
|
83
|
+
name_b: str,
|
|
84
|
+
b: Tensor,
|
|
85
|
+
response_mask: Tensor,
|
|
86
|
+
*,
|
|
87
|
+
check_b_2d: bool = False,
|
|
88
|
+
finite_suffix: str = "",
|
|
89
|
+
) -> None:
|
|
90
|
+
"""Shared shape/mask/finiteness validation for a pair of logprob streams.
|
|
91
|
+
|
|
92
|
+
Finiteness is checked only at response positions: masked padding never affects any
|
|
93
|
+
output, so non-finite junk there must not raise (mask invariance extends to
|
|
94
|
+
validation). ``check_b_2d`` additionally names ``b`` in its own rank error before
|
|
95
|
+
the shape comparison; ``finite_suffix`` (e.g. ``" (response positions)"``) is
|
|
96
|
+
appended to the finiteness labels. Both parameters preserve the callers' historical
|
|
97
|
+
error strings.
|
|
98
|
+
"""
|
|
99
|
+
check_2d(name_a, a)
|
|
100
|
+
if check_b_2d:
|
|
101
|
+
check_2d(name_b, b)
|
|
102
|
+
check_same_shape(name_a, a, name_b, b)
|
|
103
|
+
check_mask(response_mask, like=a)
|
|
104
|
+
check_finite(name_a + finite_suffix, a[response_mask])
|
|
105
|
+
check_finite(name_b + finite_suffix, b[response_mask])
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def broadcast_advantages(
|
|
109
|
+
advantages: Tensor,
|
|
110
|
+
like: Tensor,
|
|
111
|
+
response_mask: Tensor,
|
|
112
|
+
*,
|
|
113
|
+
like_name: str,
|
|
114
|
+
zero_masked: bool = False,
|
|
115
|
+
finite_label_2d: str = "advantages (response positions)",
|
|
116
|
+
batch_mismatch_template: str = "advantages [B] must have B = {b} rows; got shape {adv_shape}",
|
|
117
|
+
shape_mismatch_template: str | None = None,
|
|
118
|
+
) -> Tensor:
|
|
119
|
+
"""Validate ``[B]`` or ``[B, T]`` advantages and return them as ``[B, T]``.
|
|
120
|
+
|
|
121
|
+
A ``[B]`` input is expanded across its row's tokens; a ``[B, T]`` input must match
|
|
122
|
+
``like``'s shape and be finite at response positions. With ``zero_masked=True`` the
|
|
123
|
+
result is exactly 0 at masked positions, so padded advantage junk reaches neither
|
|
124
|
+
forward values nor backward formulas (mask invariance); with ``zero_masked=False``
|
|
125
|
+
the raw broadcast view is returned for callers that intersect with the mask
|
|
126
|
+
themselves. The message templates take ``{b}``, ``{adv_shape}``, and ``{like_shape}``
|
|
127
|
+
placeholders; ``shape_mismatch_template=None`` uses :func:`check_same_shape` with
|
|
128
|
+
``like_name``.
|
|
129
|
+
"""
|
|
130
|
+
if advantages.dim() == 1:
|
|
131
|
+
if advantages.shape[0] != like.shape[0]:
|
|
132
|
+
raise ValueError(
|
|
133
|
+
batch_mismatch_template.format(b=like.shape[0], adv_shape=tuple(advantages.shape))
|
|
134
|
+
)
|
|
135
|
+
check_finite("advantages", advantages)
|
|
136
|
+
expanded = advantages.unsqueeze(1).expand_as(like)
|
|
137
|
+
elif advantages.dim() == 2:
|
|
138
|
+
if shape_mismatch_template is None:
|
|
139
|
+
check_same_shape("advantages", advantages, like_name, like)
|
|
140
|
+
elif advantages.shape != like.shape:
|
|
141
|
+
raise ValueError(
|
|
142
|
+
shape_mismatch_template.format(
|
|
143
|
+
like_shape=tuple(like.shape), adv_shape=tuple(advantages.shape)
|
|
144
|
+
)
|
|
145
|
+
)
|
|
146
|
+
check_finite(finite_label_2d, advantages[response_mask])
|
|
147
|
+
expanded = advantages
|
|
148
|
+
else:
|
|
149
|
+
raise ValueError(f"advantages must be [B] or [B, T]; got shape {tuple(advantages.shape)}")
|
|
150
|
+
if not zero_masked:
|
|
151
|
+
return expanded
|
|
152
|
+
zero = torch.zeros((), dtype=expanded.dtype, device=expanded.device)
|
|
153
|
+
return torch.where(response_mask, expanded, zero)
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def std_or_zero(values: Tensor) -> float:
|
|
157
|
+
"""Bessel-corrected std; a single observation has no spread estimate, reported as 0.0."""
|
|
158
|
+
if values.numel() < 2:
|
|
159
|
+
return 0.0
|
|
160
|
+
return float(values.std())
|