Repository navigation
Add comprehensive output methods contract tests - #25
Merged
Merged
Conversation
- Add test_output_methods_contract.py covering all output methods (mean_pooled, per_token, logits, attention_head, attention_layer, attention_model, substring_pooled). - Enforce preallocated disk shape consistency against runtime shapes across modes. - Verify multi-layer indexing integrity (prevents layer vs vocab/batch slicing bugs). - Verify discard_padding and flatten contracts across 2D/3D output types. - Verify split_long_sequences reconstruction contracts and add variable-length stack fallback in _prepare_tensor. - Add contract test to CI integration workflow.
Contributor
There was a problem hiding this comment.
🟡 Changes recommended
A critical export error-handling issue and a moderate contract-test gap remain unresolved.
Get a fresh assessment by requesting another Copilot review.
Pull request overview
Adds contract tests for output extraction behavior and hardens variable-length tensor export handling.
Changes:
- Added regression tests for shapes, flattening, padding, splitting, pooling, and vocabulary resolution.
- Added variable-length tensor serialization fallback.
File summaries
| File | Summary | Review findings |
|---|---|---|
src/tests/test_output_methods_contract.py |
Adds output-method contract and regression tests. | Moderate (1 vote): strengthen reconstructed mean-value assertions. Nits (1, 1, and 3 votes): cover in-memory attention-head shapes, all in-memory flattened outputs, and config-only vocabulary resolution. |
src/pepe/embedders/base_embedder.py |
Handles variable-length tensors during export. | Critical (1 vote; lines 936–937): avoid catching every RuntimeError from torch.stack; distinguish unequal-size failures from other tensor errors. |
Review details
Suppressed comments (4)
src/pepe/embedders/base_embedder.py:939
- When reconstructed sequences have different lengths, this fallback returns a
dtype=objectarray, andexport_to_disk()then writes it withnp.saveas a pickle-backed.npy. Normal consumers (including the repository'snp.loadcalls, which do not setallow_pickle=True) cannot load that file and raiseValueError: Object arrays cannot be loaded when allow_pickle=False. Please pad reconstructed outputs to a common numeric shape or define and use a supported non-pickle variable-length format instead of silently producing an unusable disk artifact.
if flatten:
return np.array([t.numpy().flatten() for t in data_list], dtype=object)
return np.array([t.numpy() for t in data_list], dtype=object)
src/tests/test_output_methods_contract.py:130
- Although
expected_shapesincludesattention_head, the in-memory assertions here cover every listed output except per-head attention; only the streamed head files are checked. A regression that changesres_mem["attention_head"][LAYER][head]can therefore pass this contract test. Add a shape assertion for each head in the in-memory result.
attn_layer_arr = np.stack(
[t.numpy() for t in res_mem["attention_layer"][LAYER]]
)
self.assertEqual(attn_layer_arr.shape, expected_shapes["attention_layer"])
src/tests/test_output_methods_contract.py:208
- The flattened contract is advertised for all output types, but the in-memory portion only checks
logitsandper_token. Mean-pooled and all three attention outputs are validated only on disk, so an in-memory flattening regression in those paths would still pass. Add corresponding in-memory shape checks for the remaining output types.
# In-memory flattened checks
self.assertEqual(
np.stack([t.numpy() for t in res_mem["logits"][LAYER]]).shape,
expected_flattened_shapes["logits"],
)
self.assertEqual(
np.stack([t.numpy() for t in res_mem["per_token"][LAYER]]).shape,
expected_flattened_shapes["per_token"],
)
src/tests/test_output_methods_contract.py:353
- These assertions only check the reconstructed mean vector's rank, not its values. In the no-discard path,
_extract_mean_pooledexcludes special tokens, while_reconstruct_chunksrecomputesfull_per_token.mean(0), so BOS/EOS can be included without this test failing. Assert that the reconstructed mean equals the residue-only mean ofper_token[1:-1](and similarly cover the discard case).
self.assertEqual(res_no_discard["per_token"][LAYER][0].shape, (32, EMBED_DIM))
self.assertEqual(res_no_discard["logits"][LAYER][0].shape, (32, VOCAB_SIZE))
- Files reviewed: 2/2 changed files
- Comments generated: 2
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| if flatten: | ||
| tensor = tensor.flatten(start_dim=1) | ||
| return tensor.numpy() | ||
| except RuntimeError: |
Comment on lines
+406
to
+417
| # 4. Has alphabet with all_toks | ||
| delattr(embedder, "tokenizer") | ||
|
|
||
| class DummyAlphabet: | ||
| all_toks = ["<pad>", "A", "C", "D"] | ||
|
|
||
| embedder.alphabet = DummyAlphabet() | ||
| self.assertEqual(embedder._get_vocab_size(), 4) | ||
|
|
||
| # 5. Fallback | ||
| delattr(embedder, "alphabet") | ||
| self.assertEqual(embedder._get_vocab_size(), 0) |
- Avoid broad RuntimeError catch in _prepare_tensor by checking unequal tensor shapes beforehand. - Add test coverage for config-based vocab_size resolution in BaseEmbedder. - Add in-memory shape assertions for attention_head (all heads) in unflattened mode. - Add comprehensive in-memory shape checks for all output types in flattened mode. - Assert reconstructed mean_pooled values match per-token mean across split sequences.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
This PR adds an extensive regression and contract test suite to prevent dimension mismatch and pipeline errors across all output extraction methods and execution modes.
Key Changes
Contract Test Suite (
src/tests/test_output_methods_contract.py):_set_output_objects()exactly matches written arrays and in-memory shapes for all output types (mean_pooled,per_token,logits,attention_head,attention_layer,attention_model).flatten=Trueyields valid 2D matrices across all output types in both streaming and in-memory modes.discard_padding=Truecleanly strip padding tokens across token-level and logit outputs.split_long_sequences=Trueproperly stitches multi-chunk sequences acrossper_token,logits, andmean_pooled.substring_pooledcorrectly preserves(N, D)representations._get_vocab_size()safely resolves vocab sizes across model backends, alphabets, and configs.BaseEmbedder Hardening (
src/pepe/embedders/base_embedder.py):_prepare_tensorto safely handle variable-length sequence arrays when saving unpadded/chunked embeddings without raisingRuntimeError: stack expects each tensor to be equal size.Test Coverage
pytest src/tests/test_output_methods_contract.py(7/7 tests pass).ruff check,ruff format --check, andmypy src/with 0 issues.