feat: record token counts on tasks for energy-per-token reporting - #1362
Open
davidberenstein1957 wants to merge 3 commits into
Open
feat: record token counts on tasks for energy-per-token reporting#1362davidberenstein1957 wants to merge 3 commits into
davidberenstein1957 wants to merge 3 commits into
Conversation
Add input_tokens, output_tokens and n_requests to TaskEmissionsData, with energy_per_output_token and emissions_per_request derived from them, so LLM inference can be reported per token instead of per run. Counts are accumulated on the task via tracker.record_tokens(), which can also read them straight from an OpenAI-compatible, Ollama or vLLM response by duck typing, without importing any inference library. Closes #1347 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## master #1362 +/- ##
==========================================
+ Coverage 91.39% 91.66% +0.27%
==========================================
Files 49 49
Lines 5056 5101 +45
==========================================
+ Hits 4621 4676 +55
+ Misses 435 425 -10 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
The cross-link from reference/output.md targeted an anchor that never existed: the section was bold text, not a heading. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
- drop energy_per_output_token / emissions_per_request: properties never reach TaskEmissionsData.values, so they delivered nothing - assert the warning and the untouched counters when no task is active - debug hint when a response carries no usage (streamed OpenAI chunks) Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
davidberenstein1957
marked this pull request as ready for review
August 12, 2026 19:14
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.
What this adds
Energy per run is not a comparable number for an LLM: it depends entirely on how many prompts happened to be sent. Energy per output token is. CodeCarbon already measures the power draw over a task; the only missing ingredient was the token counter, which only the caller knows.
This PR lets the caller hand that counter over, and carries it through the existing task machinery to the output handlers.
User-facing surface
Three new fields on
TaskEmissionsData, all defaulting to0:input_tokens,output_tokens,n_requests. Two derived properties computed from them rather than stored, so they cannot go stale:energy_per_output_token(kWh) andemissions_per_request(kgCO2eq). Both return0.0rather than raising when the counter is zero.One recording entry point, on the tracker and mirrored on
TaskEmissionsTracker:response=reads what the serving stack already returns — OpenAI-compatibleusage, Ollama'sprompt_eval_count/eval_count, vLLMRequestOutput. All of it isgetattr/dict.getinextract_token_counts, so CodeCarbon imports nothing new and no dependency or extra is added. Explicitrecord_tokens(input_tokens=..., output_tokens=...)covers everything else. Counts accumulate over the life of one task; recording with no active task logs a warning and is otherwise a no-op.The three fields flow through
Task.out()and appear as three extra columns in the per-task CSV. Note that the task CSV is rewritten in full on each run (FileOutput.task_outwrites a fresh dataframe to a per-run-id path), so there is no header-migration concern for existing files.Verification
New
tests/test_token_tracking.py, 10 tests: extraction from OpenAI object and dict, Ollama dict, a vLLM-shaped stub, and an unrecognised payload; accumulation across severalrecord_tokenscalls in one task; the zero-counter guard on both derived properties; recording with no active task; and an end-to-end check that the columns land in the task CSV with the right values.tests/test_tracking_inference.py,test_logging_output.py,test_custom_handler.pyandtest_docs_examples.pystill pass.blackandruff checkare clean on the touched files (the repo-widetask lint/task formatwere not run — they currently churn ~120 unrelated files).Deliberately left out
measure_power_secs, so per-request tasks would be dominated by measurement noise. The docs warn about short tasks rather than adding a config key for the warning;log_levelalready covers silencing.EmissionsData, since a run may mix inference with other work.Docs updated: an "LLM inference, energy per token" section in
docs/tutorials/python-api.mdand the three task columns indocs/reference/output.md.Closes #1347
🤖 Generated with Claude Code