From 0be21886300cc8db2eac8dc182e89063340690fa Mon Sep 17 00:00:00 2001 From: Pavlo Kuchmiichuk Date: Tue, 21 Jul 2026 23:47:48 -0400 Subject: [PATCH 1/6] Defaults each list constraint's discriminator to its own type. --- bead/lists/constraints.py | 24 ++++++------ tests/lists/test_constraints.py | 69 +++++++++++++++++++++++++++++++++ 2 files changed, 81 insertions(+), 12 deletions(-) diff --git a/bead/lists/constraints.py b/bead/lists/constraints.py index 785e8af..8f32046 100644 --- a/bead/lists/constraints.py +++ b/bead/lists/constraints.py @@ -81,7 +81,7 @@ class UniquenessConstraint(ListConstraint): Higher values are weighted more heavily during partitioning. """ - constraint_type: typing.Literal["uniqueness"] + constraint_type: typing.Literal["uniqueness"] = "uniqueness" property_expression: str context: dict[str, ContextValue] = dx.field(default_factory=dict) allow_null: bool = False @@ -111,7 +111,7 @@ class ConditionalUniquenessConstraint(ListConstraint): Constraint priority. """ - constraint_type: typing.Literal["conditional_uniqueness"] + constraint_type: typing.Literal["conditional_uniqueness"] = "conditional_uniqueness" property_expression: str condition_expression: str context: dict[str, ContextValue] = dx.field(default_factory=dict) @@ -140,7 +140,7 @@ class BalanceConstraint(ListConstraint): Constraint priority. """ - constraint_type: typing.Literal["balance"] + constraint_type: typing.Literal["balance"] = "balance" property_expression: str context: dict[str, ContextValue] = dx.field(default_factory=dict) target_counts: dict[str, int] | None = None @@ -191,7 +191,7 @@ class QuantileConstraint(ListConstraint): Constraint priority. """ - constraint_type: typing.Literal["quantile"] + constraint_type: typing.Literal["quantile"] = "quantile" property_expression: str context: dict[str, ContextValue] = dx.field(default_factory=dict) n_quantiles: int = 5 @@ -231,7 +231,7 @@ class GroupedQuantileConstraint(ListConstraint): Constraint priority. """ - constraint_type: typing.Literal["grouped_quantile"] + constraint_type: typing.Literal["grouped_quantile"] = "grouped_quantile" property_expression: str group_by_expression: str context: dict[str, ContextValue] = dx.field(default_factory=dict) @@ -468,7 +468,7 @@ class DiversityConstraint(ListConstraint): Constraint priority. """ - constraint_type: typing.Literal["diversity"] + constraint_type: typing.Literal["diversity"] = "diversity" property_expression: str min_unique_values: int context: dict[str, ContextValue] = dx.field(default_factory=dict) @@ -505,7 +505,7 @@ class SizeConstraint(ListConstraint): Constraint priority. """ - constraint_type: typing.Literal["size"] + constraint_type: typing.Literal["size"] = "size" min_size: int | None = None max_size: int | None = None exact_size: int | None = None @@ -580,7 +580,7 @@ class OrderingConstraint(ListConstraint): Constraint priority (unused for static partitioning). """ - constraint_type: typing.Literal["ordering"] + constraint_type: typing.Literal["ordering"] = "ordering" precedence_pairs: tuple[dx.Embed[OrderingPair], ...] = () no_adjacent_property: str | None = None block_by_property: str | None = None @@ -637,7 +637,7 @@ class BatchCoverageConstraint(BatchConstraint): Constraint priority. """ - constraint_type: typing.Literal["coverage"] + constraint_type: typing.Literal["coverage"] = "coverage" property_expression: str context: dict[str, ContextValue] = dx.field(default_factory=dict) target_values: tuple[str | int | float, ...] | None = None @@ -673,7 +673,7 @@ class BatchBalanceConstraint(BatchConstraint): Constraint priority. """ - constraint_type: typing.Literal["balance"] + constraint_type: typing.Literal["balance"] = "balance" property_expression: str target_distribution: dict[str, float] context: dict[str, ContextValue] = dx.field(default_factory=dict) @@ -724,7 +724,7 @@ class BatchDiversityConstraint(BatchConstraint): Constraint priority. """ - constraint_type: typing.Literal["diversity"] + constraint_type: typing.Literal["diversity"] = "diversity" property_expression: str max_lists_per_value: int context: dict[str, ContextValue] = dx.field(default_factory=dict) @@ -757,7 +757,7 @@ class BatchMinOccurrenceConstraint(BatchConstraint): Constraint priority. """ - constraint_type: typing.Literal["min_occurrence"] + constraint_type: typing.Literal["min_occurrence"] = "min_occurrence" property_expression: str min_occurrences: int context: dict[str, ContextValue] = dx.field(default_factory=dict) diff --git a/tests/lists/test_constraints.py b/tests/lists/test_constraints.py index 2a23e74..aded254 100644 --- a/tests/lists/test_constraints.py +++ b/tests/lists/test_constraints.py @@ -9,6 +9,12 @@ from bead.lists.constraints import ( BalanceConstraint, + BatchBalanceConstraint, + BatchCoverageConstraint, + BatchDiversityConstraint, + BatchMinOccurrenceConstraint, + DiversityConstraint, + GroupedQuantileConstraint, OrderingConstraint, OrderingPair, QuantileConstraint, @@ -725,3 +731,66 @@ def test_serialization_preserves_type(self) -> None: for constraint in constraints: data = constraint.model_dump() assert "constraint_type" in data + + +class TestConstraintTypeDefault: + """Tests that constraint_type defaults to each class's own discriminator.""" + + def test_uniqueness_defaults(self) -> None: + """Test UniquenessConstraint infers its constraint_type.""" + constraint = UniquenessConstraint(property_expression="item['verb']") + assert constraint.constraint_type == "uniqueness" + + def test_balance_defaults(self) -> None: + """Test BalanceConstraint infers its constraint_type.""" + constraint = BalanceConstraint(property_expression="item['pair_type']") + assert constraint.constraint_type == "balance" + + def test_grouped_quantile_defaults(self) -> None: + """Test GroupedQuantileConstraint infers its constraint_type.""" + constraint = GroupedQuantileConstraint( + property_expression="item['score']", + group_by_expression="item['contrast']", + ) + assert constraint.constraint_type == "grouped_quantile" + + def test_diversity_defaults(self) -> None: + """Test DiversityConstraint infers its constraint_type.""" + constraint = DiversityConstraint( + property_expression="item['noun']", min_unique_values=3 + ) + assert constraint.constraint_type == "diversity" + + def test_batch_coverage_defaults(self) -> None: + """Test BatchCoverageConstraint infers its constraint_type.""" + constraint = BatchCoverageConstraint(property_expression="item['contrast']") + assert constraint.constraint_type == "coverage" + + def test_batch_balance_defaults(self) -> None: + """Test BatchBalanceConstraint infers its constraint_type.""" + constraint = BatchBalanceConstraint( + property_expression="item['pair_type']", + target_distribution={"same": 0.5, "different": 0.5}, + ) + assert constraint.constraint_type == "balance" + + def test_batch_diversity_defaults(self) -> None: + """Test BatchDiversityConstraint infers its constraint_type.""" + constraint = BatchDiversityConstraint( + property_expression="item['verb']", max_lists_per_value=4 + ) + assert constraint.constraint_type == "diversity" + + def test_batch_min_occurrence_defaults(self) -> None: + """Test BatchMinOccurrenceConstraint infers its constraint_type.""" + constraint = BatchMinOccurrenceConstraint( + property_expression="item['quantile']", min_occurrences=2 + ) + assert constraint.constraint_type == "min_occurrence" + + def test_explicit_constraint_type_still_accepted(self) -> None: + """Test passing constraint_type explicitly remains valid.""" + constraint = UniquenessConstraint( + constraint_type="uniqueness", property_expression="item['verb']" + ) + assert constraint.constraint_type == "uniqueness" From 7bb17f97b41ad575a9c39c7a28dcff1d318c4846 Mon Sep 17 00:00:00 2001 From: Pavlo Kuchmiichuk Date: Tue, 21 Jul 2026 23:47:48 -0400 Subject: [PATCH 2/6] Warns when a batch constraint property expression cannot be evaluated. --- bead/lists/partitioner.py | 40 ++++++++++++++++++++++++--- tests/lists/test_partitioner_batch.py | 31 +++++++++++++++++++++ 2 files changed, 67 insertions(+), 4 deletions(-) diff --git a/bead/lists/partitioner.py b/bead/lists/partitioner.py index fc02345..04b9ed8 100644 --- a/bead/lists/partitioner.py +++ b/bead/lists/partitioner.py @@ -7,6 +7,7 @@ from __future__ import annotations +import logging from collections import Counter, defaultdict from collections.abc import Callable, Hashable from typing import Any @@ -43,6 +44,8 @@ # without dict-invariance noise. type BalanceMetrics = dict[str, "MetadataValue"] +logger = logging.getLogger(__name__) + class ListPartitioner: """Partitions items into balanced experimental lists. @@ -80,6 +83,7 @@ def __init__(self, random_seed: int | None = None) -> None: self.random_seed = random_seed self._rng = np.random.default_rng(random_seed) self.dsl_evaluator = DSLEvaluator() + self._unresolved_expressions: set[str] = set() def partition( self, @@ -574,6 +578,30 @@ def _check_size(self, exp_list: ExperimentList, constraint: SizeConstraint) -> b return True + def _warn_unresolved(self, property_expression: str, error: Exception) -> None: + """Report a property expression that cannot be evaluated. + + Batch scoring skips items whose property cannot be read. Without a + warning a misconfigured constraint scores zero on every iteration and + silently has no effect, so each expression is reported once. + + Parameters + ---------- + property_expression : str + The expression that failed to evaluate. + error : Exception + The failure raised while evaluating it. + """ + if property_expression in self._unresolved_expressions: + return + self._unresolved_expressions.add(property_expression) + logger.warning( + "Batch constraint property expression %r could not be evaluated (%s). " + "The constraint cannot be satisfied and is being ignored.", + property_expression, + error, + ) + def _extract_property_value( self, item_id: UUID, @@ -1034,7 +1062,8 @@ def _compute_batch_coverage_score( metadata, ) observed_values.add(value) - except Exception: + except Exception as error: + self._warn_unresolved(constraint.property_expression, error) continue # Compute coverage @@ -1085,7 +1114,8 @@ def _compute_batch_balance_score( ) counts[value] += 1 total += 1 - except Exception: + except Exception as error: + self._warn_unresolved(constraint.property_expression, error) continue if total == 0: @@ -1140,7 +1170,8 @@ def _compute_batch_diversity_score( metadata, ) value_to_lists[value].add(list_idx) - except Exception: + except Exception as error: + self._warn_unresolved(constraint.property_expression, error) continue if not value_to_lists: @@ -1193,7 +1224,8 @@ def _compute_batch_min_occurrence_score( metadata, ) counts[value] += 1 - except Exception: + except Exception as error: + self._warn_unresolved(constraint.property_expression, error) continue if not counts: diff --git a/tests/lists/test_partitioner_batch.py b/tests/lists/test_partitioner_batch.py index 6996ecd..6e97007 100644 --- a/tests/lists/test_partitioner_batch.py +++ b/tests/lists/test_partitioner_batch.py @@ -2,8 +2,11 @@ from __future__ import annotations +import logging from uuid import uuid4 +import pytest + from bead.lists.constraints import ( BatchBalanceConstraint, BatchCoverageConstraint, @@ -515,3 +518,31 @@ def test_improve_with_insufficient_lists(self) -> None: ) assert improved is False + + +class TestBatchConstraintDiagnostics: + """Tests that unusable batch constraints are reported rather than ignored.""" + + def test_unresolvable_property_expression_warns( + self, caplog: pytest.LogCaptureFixture + ) -> None: + """Test a batch constraint on a missing metadata key logs a warning.""" + partitioner = ListPartitioner(random_seed=42) + items = [uuid4() for _ in range(20)] + metadata = {uid: {"template_id": i % 4} for i, uid in enumerate(items)} + constraint = BatchCoverageConstraint( + constraint_type="coverage", + property_expression="item['absent_key']", + target_values=[0, 1, 2, 3], + ) + + with caplog.at_level(logging.WARNING, logger="bead.lists.partitioner"): + partitioner.partition_with_batch_constraints( + items=items, + n_lists=4, + batch_constraints=[constraint], + metadata=metadata, + ) + + assert caplog.records, "expected a warning about the unusable constraint" + assert any("absent_key" in record.getMessage() for record in caplog.records) From 4159febe45168b5b1812cca4cb671d1a5031a5e3 Mon Sep 17 00:00:00 2001 From: Pavlo Kuchmiichuk Date: Wed, 22 Jul 2026 12:05:58 -0400 Subject: [PATCH 3/6] Warns when a list constraint type is not enforced during assignment. --- bead/lists/partitioner.py | 31 +++++++++++++++++++++++++++---- tests/lists/test_partitioner.py | 27 +++++++++++++++++++++++++++ 2 files changed, 54 insertions(+), 4 deletions(-) diff --git a/bead/lists/partitioner.py b/bead/lists/partitioner.py index 04b9ed8..0d58ad6 100644 --- a/bead/lists/partitioner.py +++ b/bead/lists/partitioner.py @@ -84,6 +84,7 @@ def __init__(self, random_seed: int | None = None) -> None: self._rng = np.random.default_rng(random_seed) self.dsl_evaluator = DSLEvaluator() self._unresolved_expressions: set[str] = set() + self._unenforced_types: set[str] = set() def partition( self, @@ -452,6 +453,8 @@ def _count_violations( elif isinstance(constraint, SizeConstraint): if not self._check_size(exp_list, constraint): is_violated = True + else: + self._warn_unenforced(str(constraint.constraint_type)) if is_violated: priority = constraint.priority @@ -581,9 +584,9 @@ def _check_size(self, exp_list: ExperimentList, constraint: SizeConstraint) -> b def _warn_unresolved(self, property_expression: str, error: Exception) -> None: """Report a property expression that cannot be evaluated. - Batch scoring skips items whose property cannot be read. Without a - warning a misconfigured constraint scores zero on every iteration and - silently has no effect, so each expression is reported once. + Scoring skips items whose property cannot be read. Without a warning a + misconfigured constraint scores zero on every iteration and silently has + no effect, so each expression is reported once. Parameters ---------- @@ -596,12 +599,32 @@ def _warn_unresolved(self, property_expression: str, error: Exception) -> None: return self._unresolved_expressions.add(property_expression) logger.warning( - "Batch constraint property expression %r could not be evaluated (%s). " + "Constraint property expression %r could not be evaluated (%s). " "The constraint cannot be satisfied and is being ignored.", property_expression, error, ) + def _warn_unenforced(self, constraint_type: str) -> None: + """Report a constraint type that assignment cannot enforce. + + Unhandled types are stored on the list and serialised, so without a + warning a configured constraint appears active while having no effect. + + Parameters + ---------- + constraint_type : str + Discriminator of the constraint that is not enforced. + """ + if constraint_type in self._unenforced_types: + return + self._unenforced_types.add(constraint_type) + logger.warning( + "List constraint type %r is not enforced during assignment. It is " + "recorded on the list but does not influence partitioning.", + constraint_type, + ) + def _extract_property_value( self, item_id: UUID, diff --git a/tests/lists/test_partitioner.py b/tests/lists/test_partitioner.py index b2d3ae9..b894802 100644 --- a/tests/lists/test_partitioner.py +++ b/tests/lists/test_partitioner.py @@ -2,6 +2,7 @@ from __future__ import annotations +import logging from uuid import UUID, uuid4 import didactic.api as dx @@ -11,6 +12,7 @@ from bead.lists import ExperimentList from bead.lists.constraints import ( BalanceConstraint, + GroupedQuantileConstraint, QuantileConstraint, SizeConstraint, UniquenessConstraint, @@ -573,3 +575,28 @@ def test_constraint_priority_default() -> None: constraint_type="quantile", property_expression="item['score']" ) assert constraint4.priority == 1 + + +class TestUnenforcedConstraintWarning: + """Tests that constraint types the partitioner cannot enforce are reported.""" + + def test_unenforced_constraint_type_warns( + self, caplog: pytest.LogCaptureFixture + ) -> None: + """Test an unenforced constraint type warns instead of passing silently.""" + partitioner = ListPartitioner(random_seed=42) + items = [uuid4() for _ in range(4)] + metadata: dict[UUID, ItemMetadata] = {uid: {"score": 1.0} for uid in items} + exp_list = ExperimentList(name="list_0", list_number=0, item_refs=tuple(items)) + constraint = GroupedQuantileConstraint( + property_expression="item['score']", + group_by_expression="item['score']", + ) + + with caplog.at_level(logging.WARNING, logger="bead.lists.partitioner"): + partitioner._count_violations(exp_list, [constraint], metadata) + + assert caplog.records, "expected a warning about the unenforced constraint" + assert any( + "grouped_quantile" in record.getMessage() for record in caplog.records + ) From 3a907455794bc6b057f9f53f126d8d07999b915d Mon Sep 17 00:00:00 2001 From: Pavlo Kuchmiichuk Date: Wed, 22 Jul 2026 12:42:36 -0400 Subject: [PATCH 4/6] Loads causal language models in the checkpoint's own dtype. --- bead/items/adapters/huggingface.py | 10 +++++- tests/items/adapters/test_huggingface.py | 46 ++++++++++++++++++++++++ 2 files changed, 55 insertions(+), 1 deletion(-) diff --git a/bead/items/adapters/huggingface.py b/bead/items/adapters/huggingface.py index 6f74a0c..8650b16 100644 --- a/bead/items/adapters/huggingface.py +++ b/bead/items/adapters/huggingface.py @@ -59,6 +59,10 @@ class HuggingFaceLanguageModel(HuggingFaceAdapterMixin, ModelAdapter): Device to run model on. Falls back to CPU if device unavailable. model_version : str Version string for cache tracking. + dtype : str + Torch dtype to load the weights in, such as ``"bfloat16"``. Defaults to + ``"auto"``, which keeps the dtype the checkpoint was saved in; loading a + half-precision checkpoint as float32 would double its memory. Examples -------- @@ -77,9 +81,11 @@ def __init__( cache: ModelOutputCache, device: DeviceType = "cpu", model_version: str = "unknown", + dtype: str = "auto", ) -> None: super().__init__(model_name, cache, model_version) self.device = self._validate_device(device) + self.dtype = dtype self._model: PreTrainedModel | None = None self._tokenizer: PreTrainedTokenizerBase | None = None @@ -87,7 +93,9 @@ def _load_model(self) -> None: """Load model and tokenizer lazily on first use.""" if self._model is None: logger.info(f"Loading causal LM: {self.model_name}") - self._model = AutoModelForCausalLM.from_pretrained(self.model_name) + self._model = AutoModelForCausalLM.from_pretrained( + self.model_name, dtype=self.dtype + ) self._model.to(self.device) self._model.eval() diff --git a/tests/items/adapters/test_huggingface.py b/tests/items/adapters/test_huggingface.py index 4fffd72..6be85ac 100644 --- a/tests/items/adapters/test_huggingface.py +++ b/tests/items/adapters/test_huggingface.py @@ -41,6 +41,52 @@ def test_gpt2_initialization( assert adapter.cache is in_memory_cache +def test_gpt2_loads_with_requested_dtype( + mocker: MockerFixture, + mock_gpt2_model: pytest.fixture, + mock_gpt2_tokenizer: pytest.fixture, + in_memory_cache: ModelOutputCache, +) -> None: + """Test the requested dtype is passed through when loading the model.""" + load = mocker.patch( + "bead.items.adapters.huggingface.AutoModelForCausalLM.from_pretrained", + return_value=mock_gpt2_model, + ) + mocker.patch( + "bead.items.adapters.huggingface.AutoTokenizer.from_pretrained", + return_value=mock_gpt2_tokenizer, + ) + + adapter = HuggingFaceLanguageModel( + "gpt2", in_memory_cache, device="cpu", dtype="bfloat16" + ) + adapter._load_model() + + assert load.call_args.kwargs["dtype"] == "bfloat16" + + +def test_gpt2_defaults_to_checkpoint_dtype( + mocker: MockerFixture, + mock_gpt2_model: pytest.fixture, + mock_gpt2_tokenizer: pytest.fixture, + in_memory_cache: ModelOutputCache, +) -> None: + """Test the checkpoint's own dtype is used when none is requested.""" + load = mocker.patch( + "bead.items.adapters.huggingface.AutoModelForCausalLM.from_pretrained", + return_value=mock_gpt2_model, + ) + mocker.patch( + "bead.items.adapters.huggingface.AutoTokenizer.from_pretrained", + return_value=mock_gpt2_tokenizer, + ) + + adapter = HuggingFaceLanguageModel("gpt2", in_memory_cache, device="cpu") + adapter._load_model() + + assert load.call_args.kwargs["dtype"] == "auto" + + def test_gpt2_compute_log_probability( mocker: MockerFixture, mock_gpt2_model: pytest.fixture, From 20f4de74ab418a6cf2bf228b49254e8622688528 Mon Sep 17 00:00:00 2001 From: Pavlo Kuchmiichuk Date: Wed, 22 Jul 2026 12:42:36 -0400 Subject: [PATCH 5/6] Passes the requested dtype from the scorer to the language model. --- bead/items/scoring.py | 6 ++++++ tests/items/test_scoring.py | 12 ++++++++++++ 2 files changed, 18 insertions(+) diff --git a/bead/items/scoring.py b/bead/items/scoring.py index 2b0f6c5..4bd4951 100644 --- a/bead/items/scoring.py +++ b/bead/items/scoring.py @@ -134,6 +134,9 @@ class LanguageModelScorer(ItemScorer): Key in item.rendered_elements to use as text (default: "text"). model_version : str Version string for cache tracking. + dtype : str + Torch dtype to load the weights in, such as ``"bfloat16"``. Defaults to + ``"auto"``, keeping the dtype the checkpoint was saved in. Examples -------- @@ -155,12 +158,14 @@ def __init__( device: str = "cpu", text_key: str = "text", model_version: str = "unknown", + dtype: str = "auto", ) -> None: self.model_name = model_name self.cache_dir = Path(cache_dir) if cache_dir else None self.device = device self.text_key = text_key self.model_version = model_version + self.dtype = dtype # lazy loading of model and cache self._model: HuggingFaceLanguageModel | None = None @@ -193,6 +198,7 @@ def model(self) -> HuggingFaceLanguageModel: cache=self._cache, device=self.device, # type: ignore[arg-type] model_version=self.model_version, + dtype=self.dtype, ) return self._model diff --git a/tests/items/test_scoring.py b/tests/items/test_scoring.py index 7270ace..e87312b 100644 --- a/tests/items/test_scoring.py +++ b/tests/items/test_scoring.py @@ -93,6 +93,18 @@ def test_initialization_with_string_cache_dir(self) -> None: assert scorer.cache_dir == Path(".cache/test") + def test_dtype_defaults_to_auto(self) -> None: + """Test the scorer keeps the checkpoint's own dtype by default.""" + scorer = LanguageModelScorer(model_name="gpt2", device="cpu") + + assert scorer.dtype == "auto" + + def test_dtype_is_configurable(self) -> None: + """Test a requested dtype is recorded for the adapter.""" + scorer = LanguageModelScorer(model_name="gpt2", device="cpu", dtype="bfloat16") + + assert scorer.dtype == "bfloat16" + def test_initialization_with_none_cache_dir(self) -> None: """Test initialization with None cache_dir.""" scorer = LanguageModelScorer(model_name="gpt2", cache_dir=None, device="cpu") From 6ec7cb0eb1e29607b39cf06447e4c00b691d63b0 Mon Sep 17 00:00:00 2001 From: Pavlo Kuchmiichuk Date: Fri, 24 Jul 2026 18:30:35 -0400 Subject: [PATCH 6/6] Bumps the version to 0.9.0 and records the core changes in the changelog. --- CHANGELOG.md | 21 ++++++++++++++++++++- bead/__init__.py | 2 +- pyproject.toml | 2 +- 3 files changed, 22 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b2df7b8..1090eee 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,24 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [0.9.0] - 2026-07-24 + +### Added + +- `HuggingFaceLanguageModel` and `LanguageModelScorer` accept a `dtype` parameter + (default `"auto"`) selecting the precision causal language-model weights load in; + `LanguageModelScorer` threads it through to the adapter. +- `ListPartitioner` warns, once per offending expression or type, when a constraint + property expression cannot be evaluated or a constraint type is not enforced during + assignment, so a misconfigured constraint no longer fails silently. + +### Changed + +- `ListConstraint` subclasses now default `constraint_type` to their own discriminator + value, so it no longer has to be restated when constructing a constraint. +- Causal language models load in the checkpoint's own dtype by default rather than being + forced to float32, halving the memory used by a half-precision checkpoint. + ## [0.8.0] - 2026-07-13 ### Added @@ -647,7 +665,8 @@ guards as type-checkers. - CI/CD: GitHub Actions for testing, docs, PyPI publishing - Read the Docs integration -[Unreleased]: https://github.com/FACTSlab/bead/compare/v0.8.0...HEAD +[Unreleased]: https://github.com/FACTSlab/bead/compare/v0.9.0...HEAD +[0.9.0]: https://github.com/FACTSlab/bead/compare/v0.8.0...v0.9.0 [0.8.0]: https://github.com/FACTSlab/bead/compare/v0.7.0...v0.8.0 [0.7.0]: https://github.com/FACTSlab/bead/compare/v0.6.0...v0.7.0 [0.6.0]: https://github.com/FACTSlab/bead/compare/v0.5.0...v0.6.0 diff --git a/bead/__init__.py b/bead/__init__.py index fce6111..c3633e3 100644 --- a/bead/__init__.py +++ b/bead/__init__.py @@ -6,6 +6,6 @@ from __future__ import annotations -__version__ = "0.7.0" +__version__ = "0.9.0" __author__ = "Aaron Steven White" __email__ = "aaron.white@rochester.edu" diff --git a/pyproject.toml b/pyproject.toml index 566a460..c724b2b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "hatchling.build" [project] name = "bead" -version = "0.8.0" +version = "0.9.0" description = "Lexicon and Template Collection Construction Pipeline for Acceptability and Inference Judgment Data" authors = [{name = "Aaron Steven White", email = "aaron.white@rochester.edu"}] readme = "README.md"