Skip to content

feat: record token counts on tasks for energy-per-token reporting - #1362

Open
davidberenstein1957 wants to merge 3 commits into
masterfrom
feat/llm-token-tracking
Open

feat: record token counts on tasks for energy-per-token reporting#1362
davidberenstein1957 wants to merge 3 commits into
masterfrom
feat/llm-token-tracking

Conversation

@davidberenstein1957

Copy link
Copy Markdown
Collaborator

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 to 0: 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) and emissions_per_request (kgCO2eq). Both return 0.0 rather than raising when the counter is zero.

One recording entry point, on the tracker and mirrored on TaskEmissionsTracker:

with TaskEmissionsTracker(task_name="llama3.1:8b", tracker=tracker) as task:
    for prompt in prompts:
        response = client.chat.completions.create(...)
        task.record_tokens(response=response)

response= reads what the serving stack already returns — OpenAI-compatible usage, Ollama's prompt_eval_count / eval_count, vLLM RequestOutput. All of it is getattr / dict.get in extract_token_counts, so CodeCarbon imports nothing new and no dependency or extra is added. Explicit record_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_out writes 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 several record_tokens calls 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.py and test_docs_examples.py still pass.

black and ruff check are clean on the touched files (the repo-wide task lint / task format were not run — they currently churn ~120 unrelated files).

Deliberately left out

  • No prefill/decode split — attributing those needs sub-second sampling the current scheduler cannot deliver.
  • No per-request task granularity. A single request is typically far shorter than 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_level already covers silencing.
  • No vendor-specific machinery and no optional dependency on any serving library.
  • Token counts stay task-level and do not propagate to run-level EmissionsData, since a run may mix inference with other work.
  • Continuous batching makes per-request attribution physically meaningless; that is documented as a limitation rather than papered over.

Docs updated: an "LLM inference, energy per token" section in docs/tutorials/python-api.md and the three task columns in docs/reference/output.md.

Closes #1347

🤖 Generated with Claude Code

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

codecov Bot commented Aug 12, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 91.66%. Comparing base (065d0e6) to head (cc1a610).

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.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

davidberenstein1957 and others added 2 commits August 12, 2026 19:07
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
davidberenstein1957 marked this pull request as ready for review August 12, 2026 19:14
@davidberenstein1957
davidberenstein1957 requested a review from a team as a code owner August 12, 2026 19:14
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.

Feature proposal: record token counts on tasks for energy-per-token reporting

1 participant