fix(magic): score per-query MAGIC by document, not by row (DataStream variant) - #417
Closed
luciaquirke wants to merge 2 commits into
Closed
fix(magic): score per-query MAGIC by document, not by row (DataStream variant)#417luciaquirke wants to merge 2 commits into
luciaquirke wants to merge 2 commits into
Conversation
A chunked query set (`query.chunk_length > 0`) carries a per-token
`doc_ids` column: a row can pack several documents and a document can
span several rows. `compute_per_query_magic_scores` selected row `qi`
for query `qi` and sized the stream's weights by row, so `DataStream`
— which indexes the weights by document id whenever the batch has a
`doc_ids` column — walked off the end:
IndexError: index 2 is out of bounds for dimension 0 with size 2
bergson/magic/data_stream.py:132 self.weights[indices]
Repro: `bergson magic <run> --query.chunk_length 32 ...`.
Teach `DataStream` about documents instead. It already owns the
`doc_ids` convention, so `doc_rows()` lives beside it, and two optional
arguments make a stream over one document: `rows` restricts it to those
dataset rows and `doc_id` masks every other document's tokens out of
the labels. Each score column is then that document's own mean
cross-entropy, the unit `validate_scores` and `per_doc_query_losses`
already score against. `shift_loss_mask` is rebuilt with the labels
since it is the loss denominator.
`rows` cycles through the document's rows rather than appending dead
pad ones — a pad row is a real term in the query loss, and an all-pad
batch on some rank would silently scale the query gradient down. With
one row per document this repeats that row, exactly as
`pad_dataset_to_batch_size` did, so unchunked runs are unchanged.
Chunking drops the tail that doesn't fill a chunk, so a short document
can leave no tokens behind; those score zeros, matching the zero
baseline loss `per_doc_query_losses` gives them. Their correlation is
undefined, so the reported mean Spearman now averages the queries that
have one and reports the nan count.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Collaborator
Author
|
Test sweep from the PR description, now finished — all green:
Also verified the "identical scores to #414" claim directly rather than by eye. Same job on both branches ( So the choice between this and #414 is purely about where the document logic lives, not about behaviour. |
The body restated the chunking behaviour the function's own body shows, and led with "Row indices holding each document's tokens", which reads as a document-to-row lookup without saying which direction or what a row is. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
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.
Alternative to #414 — same bug, same semantics, fixed inside
DataStreaminstead of by rebuilding a dataset per query. Open both, merge one.The bug (identical in both)
Per-query MAGIC crashes on a chunked query set (
--query.chunk_length 32):A chunked query set carries a per-token
doc_idscolumn: a row can pack several documents and a document can span several rows, so queryiis not rowi.compute_per_query_magic_scoresselected rowqiand sized the per-query weights by row, whileDataStreamindexes weights by document id wheneverdoc_idsis present.Repro:
bergson magic runs/x --model EleutherAI/pythia-14m \ --data.dataset Salesforce/wikitext --data.subset wikitext-2-raw-v1 \ --data.split "train[:96]" --data.chunk_length 32 \ --query.dataset Salesforce/wikitext --query.subset wikitext-2-raw-v1 \ --query.split "train[300:320]" --query.chunk_length 32 --batch_size 2How this differs from #414
DataStreamis already the only thing in the codebase that interpretsdoc_ids, so the document logic goes there:doc_rows(dataset, num_docs)— which rows hold each document's tokens (moved here fromcli.py).DataStream(..., rows=..., doc_id=...)—rowsrestricts the stream to those dataset rows, cycling them to fill whole batches;doc_idmasks every other document's tokens out of the labels in__getitem__, three lines wherepad_and_tensorhas just produced them.shift_loss_maskis rebuilt alongside the labels. That's load-bearing: it is the denominator inweighted_causal_lm_ce'smeanreduction (bergson/utils/math.py), so leaving it stale would divide each document's loss by the whole row's token count and scale every score column differently. The same fact rules out the tempting "just use the existing 2-D per-token weights as the mask" approach — weights affect the numerator only.Versus #414, which builds a masked mini-dataset per query (
select→remove_columns→add_column):query_doc_batchdisappears; the call site is oneDataStream(...)constructionlabels-column collision to guard againstbatch_rowsslicesself.rowsinstead of computing a range — a no-op whenrowsdefaults to every row, and its only external caller isper_doc_query_lossesCost:
DataStreamis shared by every training and backward path, so the blast radius is wider than #414's, which is confined to the per-query function.Shared with #414
Padding cycles through the document's own rows instead of appending dead ones:
compute_query_gradientsdiscards the data weights that would silence a pad row, so every row is a real term in the loss, and an all-pad batch on some rank would scale the query gradient by1/world_size. With one row per document this repeats that row exactly aspad_dataset_to_batch_sizedid, so unchunked runs are unchanged.Documents that chunking dropped entirely (blank wikitext lines) score zeros, matching the zero baseline loss
per_doc_query_lossesgives them; their correlation is undefined, so the reported mean is nowMean Spearman 1.0000 (9/20 nan)instead ofnan.Testing
--noskip_validationand viabergson validateafterwards, producing byte-identical scores to fix(magic): score per-query MAGIC by document, not by row #414.pytest tests/test_per_query_magic.pygreen; the wider sweep over everything that touchesDataStream(test_magic,test_ddp,test_attribute_tokens,test_multi_query_validate,test_per_token_lds) was still running at PR time — I'll report in a comment. No GPU on this machine, so multi-GPU tests are unrun.🤖 Generated with Claude Code