LAM-1909: Evals – stream datapoints in batches instead of retaining full dataset - #313
Open
laminar-coding-agent[bot] wants to merge 2 commits into
Open
LAM-1909: Evals – stream datapoints in batches instead of retaining full dataset#313laminar-coding-agent[bot] wants to merge 2 commits into
laminar-coding-agent[bot] wants to merge 2 commits into
Conversation
…ataset Fetching was already batched for LaminarDataset, but everything fetched and every result datapoint was retained in memory until the end of the run, putting memory pressure on huge datasets: - LaminarDataset gains a streaming __iter__ that serves already-cached items first, then pulls the remaining pages of fetch_size without appending them to the in-memory cache. __getitem__/__len__ keep their accumulate-and-cache behavior, and the EvaluationDataset ABC gets a default __iter__ over __getitem__, so custom datasets keep working. - _evaluate_in_batches keeps the semaphore sliding-window concurrency (no barrier between batches) but aggregates score sums/counts incrementally instead of gathering all EvaluationResultDatapoints, and prunes finished eval/upload tasks as the run advances. - The iterator is advanced via run_in_executor since dataset page pulls use the sync client and would otherwise block in-flight datapoints. No public API changes: evaluate()'s return shape, get_average_scores, and dataset random access are unchanged. LAM-1909 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Contributor
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 529797c. Configure here.
prune_finished was re-raising a failed task's exception mid-loop, which stopped consuming the dataset iterator, so datapoints not yet pulled were never scheduled. Record the first error instead and raise it only after all datapoints have been scheduled and run — matching the previous behavior where every task was created before gather surfaced errors. Addresses cursor bugbot review on #313. Co-Authored-By: Claude Fable 5 <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.

Why
For huge datasets, running evals put memory pressure on the machine. Fetching was already batched (
LaminarDatasetpullsfetch_sizepages lazily), but everything fetched — and everyEvaluationResultDatapointwith its full data/target/output payloads — was retained in memory until the end of the run. The problem was retention, not eager fetching.What
LaminarDataset.__iter__(new): streams the dataset — serves items already cached by__len__/__getitem__first, then pulls the remaining pages offetch_sizewithout appending them to the in-memory cache.__getitem__/__len__keep their accumulate-and-cache behavior for random access.EvaluationDatasetABC: gains a default__iter__over__getitem__, so existing custom datasets keep working unchanged._evaluate_in_batches: keeps the semaphore sliding-window concurrency (up toconcurrency_limitdatapoints in flight, no barrier at batch boundaries — one slow datapoint never stalls the window), but now:run_in_executor, since page pulls use the sync client and would otherwise block all in-flight datapoints on the event loop.Peak memory is now bounded by
concurrency_limitin-flight datapoints + onefetch_sizepage (+ the first page cached by the progress bar'slen()call), independent of dataset size.No breaking changes
evaluate()return shape (EvaluationRunResult) is unchanged.get_average_scoresremains public and unchanged (no longer used internally).__len__/__getitem__/slicesemantics are unchanged.Test evidence
LaminarDataset(asserts page count and that streamed items aren't retained), concurrency window assertion (max_in_flight == concurrency_limit),__iter__streaming/cache-first behavior, ABC default__iter__.tests/test_evaluations.py: 13 passed.test_langchain.py::test_langchain_langgraph, which fails identically on a cleanmaincheckout in this sandbox (pre-existing, unrelated).LAM-1909
🤖 Generated with Claude Code
Note
Medium Risk
Core eval orchestration and dataset iteration changed; behavior is intended to be compatible but concurrency, error timing, and memory semantics deserve careful review on large runs.
Overview
Large eval runs no longer keep the full dataset and every
EvaluationResultDatapointin memory until the end.LaminarDatasetgains a streaming__iter__: it yields anything already cached by__len__/__getitem__, then pullsfetch_sizepages from the API and yields them without appending to_fetched_items. Random access via__getitem__/__len__is unchanged.EvaluationDatasetgets a default__iter__over__getitem__so custom datasets still work withevaluate()._evaluate_in_batchesnow walksiter(self.data)in a semaphore sliding window (sameconcurrency_limitbehavior). It aggregates score sums/counts per completed datapoint and returns averages directly instead of gathering all result objects. Finished eval and upload tasks are pruned as the loop runs. Because dataset pulls are sync, the iterator advances viarun_in_executorso page fetches don’t block other in-flight datapoints. A failing datapoint still schedules the rest; the first error is re-raised after everything finishes.evaluate()return shape is unchanged;get_average_scoresstays public but isn’t used internally.CLAUDE.mddocuments the eval memory model.Reviewed by Cursor Bugbot for commit cae673a. Bugbot is set up for automated code reviews on this repo. Configure here.