Skip to content

Skip per-case force_flush when no scorer/classifier uses trace: - #211

Draft
Wil Alvarez (satanas) wants to merge 1 commit into
braintrustdata:mainfrom
satanas:skip-per-case-flush-when-unused
Draft

Skip per-case force_flush when no scorer/classifier uses trace:#211
Wil Alvarez (satanas) wants to merge 1 commit into
braintrustdata:mainfrom
satanas:skip-per-case-flush-when-unused

Conversation

@satanas

Copy link
Copy Markdown

Fixes #210

What

Runner#run_eval_case calls eval_context.tracer_provider.force_flush synchronously after every case, unconditionally, before running scorers/classifiers. This exists so a trace:-consuming scorer/classifier can BTQL-query that case's spans immediately with a freshness guarantee - but it runs even when nothing in the eval declares trace: at all.

Impact (see #210 for full numbers)

Measured 10-25s+ per force_flush call against a ~139-case eval with no trace:-consuming scorers, dwarfing typical per-case task/scorer time. Increasing parallelism didn't help (one flush got slower, 48s, under concurrency).

Fix

Scorer/Classifier already expose #call_parameters for KeywordFilter's own kwarg-filtering. This reuses that same introspection: Runner#initialize computes once whether any registered scorer or classifier declares trace: (or accepts arbitrary **kwargs, which might use it), and the existing flush/build_trace call is skipped entirely when nothing does.

Nothing about the async BatchSpanProcessor export path changes - spans still export every ~5s regardless, and the SDK's own at_exit flush still guarantees final delivery. This only removes the synchronous per-case wait for evals that never look at trace:.

Testing

  • All existing tests pass, including the trace:-behavior tests in runner_test.rb (test_scorer_declaring_trace_receives_eval_trace, test_trace_works_with_parallelism, etc.) - none of them changed behavior, since they all declare trace: and are correctly detected as needing it.
  • Added two new tests asserting the actual optimization: force_flush is not called when no scorer declares trace:, and still is called (once per case) when one does.
  • Full suite: 1302 runs, 1978 assertions, 0 failures, 0 errors (plus the 2 new tests).
  • standardrb clean on both changed files.

Open to a different shape here (e.g. an explicit opt-out flag instead of auto-detection) if that's preferred - wanted to lead with a working reference implementation since I already had the repro in hand.

Runner#run_eval_case force-flushes the tracer_provider synchronously
after every case so a trace:-consuming scorer/classifier can BTQL-query
it immediately. That flush runs unconditionally today, even when
nothing in the eval declares trace: at all, and measures at 10-25s+ per
case in practice - dwarfing typical task/scorer time for evals that
don't need it.

Scorer/Classifier already expose #call_parameters for KeywordFilter's
own introspection; reuse it here to detect whether anything registered
actually wants trace: (or accepts **kwargs, which might), and skip the
flush and the (otherwise-unused) Trace object entirely when nothing
does. The async BatchSpanProcessor still exports normally, and the
SDK's own at_exit flush still guarantees final delivery, so no data is
lost.

Fixes braintrustdata#210
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Eval::Runner's per-case force_flush adds 10-25s+ per case even when no scorer/classifier uses trace:

1 participant