Skip to content

Add CodSpeed performance benchmarks for core DSPy operations - #54

Open
codspeed-hq[bot] wants to merge 1 commit into
mainfrom
codspeed-wizard-1779472752334
Open

Add CodSpeed performance benchmarks for core DSPy operations#54
codspeed-hq[bot] wants to merge 1 commit into
mainfrom
codspeed-wizard-1779472752334

Conversation

@codspeed-hq

@codspeed-hq codspeed-hq Bot commented May 22, 2026

Copy link
Copy Markdown

Summary

This PR sets up continuous performance tracking for DSPy using CodSpeed. It adds 41 benchmarks targeting the most performance-critical, CPU-bound code paths in the framework, along with a dedicated GitHub Actions workflow that runs on every push and pull request.

Changes

Benchmarks (tests/benchmarks/test_benchmarks.py)

41 benchmarks organized across four areas:

Example / Prediction -- Core data container operations that run on every DSPy call:

  • Creation (from kwargs and from dict), field access, copy, with_inputs, inputs/labels splitting
  • toDict recursive serialization, hash computation, dict-like iteration
  • Prediction creation and score arithmetic

Signature -- Parsing and manipulation of DSPy signatures:

  • make_signature from simple and complex typed strings (AST parsing)
  • Field manipulation: append, prepend, delete, with_instructions
  • State serialization (dump_state/load_state), equality comparison
  • infer_prefix for camelCase and snake_case conversion

Adapter formatting and parsing -- The ChatAdapter hot path that runs on every LM call:

  • format_field_description, format_field_structure, format_user_message_content, format_assistant_message_content
  • parse for single-field and multi-field completions
  • format_field_value (string and list), serialize_for_json, translate_field_type
  • parse_value for string, int, and list types
  • get_annotation_name, get_field_description_string

Serialization -- Deep copy and JSON roundtrip for nested structures

CI Workflow (.github/workflows/codspeed.yml)

  • Runs on push to main, pull requests, and workflow_dispatch (for CodSpeed backtesting)
  • Uses OIDC authentication (no tokens to manage)
  • Uses simulation mode for deterministic, low-variance measurements with automatic flamegraph generation
  • Follows the existing workflow conventions (commit hash pinning, uv caching)

Other

  • Added pytest-codspeed as a dev dependency
  • Added CodSpeed badge to README

Next steps

  • Once merged, CodSpeed will automatically run a backtest to establish baseline performance data.
  • Performance checks will appear on subsequent pull requests, flagging regressions before they ship.
  • Flamegraphs are available for each benchmark to help diagnose hotspots.
  • Additional benchmarks can be added over time as new performance-sensitive code paths are identified (e.g., optimizer loops, teleprompter operations).

- name: Install dependencies
run: uv sync --dev -p .venv --extra dev
- name: Run benchmarks
uses: CodSpeedHQ/action@b16b7f2241a8564d005126c814839e9e990045a0 # v4
- name: Install dependencies
run: uv sync --dev -p .venv --extra dev
- name: Run benchmarks
uses: CodSpeedHQ/action@b16b7f2241a8564d005126c814839e9e990045a0 # v4
@codspeed-hq

codspeed-hq Bot commented May 22, 2026

Copy link
Copy Markdown
Author

Congrats! CodSpeed is installed 🎉

🆕 41 new benchmarks were detected.

You will start to see performance impacts in the reports once the benchmarks are run from your default branch.

Detected benchmarks


ℹ️ Only the first 20 benchmarks are displayed. Go to the app to view all benchmarks.


Open in CodSpeed

@codspeed-hq
codspeed-hq Bot marked this pull request as ready for review May 22, 2026 18:08
@greptile-apps

greptile-apps Bot commented May 22, 2026

Copy link
Copy Markdown

Greptile Summary

This PR introduces continuous performance tracking for DSPy by adding 41 pytest-codspeed benchmarks and a dedicated GitHub Actions workflow that runs on every push and pull request using OIDC authentication and simulation mode.

  • Benchmarks (tests/benchmarks/test_benchmarks.py): Cover four areas — Example/Prediction container ops, Signature parsing and manipulation, ChatAdapter formatting/parsing, and serialization. One benchmark (test_example_json_roundtrip) omits json.loads despite its "roundtrip" name, and an unused fixture (chat_completion_text) silently triggers heavy DSPy metaclass setup on every test collection run.
  • CI workflow (.github/workflows/codspeed.yml): Follows existing workflow conventions (pinned action hashes, uv caching, persist-credentials: false); pytest-codspeed is correctly added under [dependency-groups] rather than the installable extras.
  • Lockfile: Adds pytest-codspeed 5.0.3 and its transitive deps (rich, markdown-it-py, mdurl) with proper hashes.

Confidence Score: 4/5

Safe to merge; the workflow and dependency changes are solid, and the only substantive defect is confined to one benchmark function that under-measures what it claims to measure.

The json roundtrip benchmark silently omits deserialization — any future performance analysis relying on that metric will see only half the cost and could lead to incorrect optimization conclusions. All other benchmarks, the CI workflow, and the dependency additions look correct and consistent with existing conventions.

tests/benchmarks/test_benchmarks.py — specifically the roundtrip benchmark and the unused fixture.

Important Files Changed

Filename Overview
tests/benchmarks/test_benchmarks.py 41 benchmarks covering Example, Prediction, Signature, ChatAdapter, and serialization paths. The json roundtrip benchmark is incomplete (missing json.loads), and an unused fixture silently runs heavy setup code on every collection.
.github/workflows/codspeed.yml New CI workflow that runs benchmarks via CodSpeedHQ/action in simulation mode; uses OIDC auth (no secrets to rotate), pinned hashes, and consistent uv setup matching existing workflows.
pyproject.toml Adds a new [dependency-groups].dev section containing pytest-codspeed>=5.0.3; naming is distinct from [project.optional-dependencies].dev and is the standard uv mechanism for dev-only tools.
README.md Adds a CodSpeed performance badge and removes a trailing blank line; straightforward documentation change.
uv.lock Lockfile updated to include pytest-codspeed 5.0.3 and its transitive dependencies (rich 15.0.0, markdown-it-py 4.2.0, mdurl 0.1.2); all entries have proper hashes and upload timestamps.
tests/benchmarks/init.py Empty init file making tests/benchmarks a proper Python package for pytest discovery.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[GitHub Event\npush / PR / workflow_dispatch] --> B[codspeed.yml workflow]
    B --> C[Checkout + setup-python 3.12]
    C --> D[Install uv + create .venv]
    D --> E[uv sync --dev --extra dev]
    E --> F[CodSpeedHQ/action v4\nmode: simulation]
    F --> G[uv run pytest tests/benchmarks/ --codspeed]
    G --> H1[TestExampleBenchmarks\n11 benchmarks]
    G --> H2[TestSignatureBenchmarks\n11 benchmarks]
    G --> H3[TestAdapterBenchmarks\n16 benchmarks]
    G --> H4[TestSerializationBenchmarks\n3 benchmarks]
    H1 & H2 & H3 & H4 --> I[CodSpeed\nPerformance Report + Flamegraphs]
Loading

Reviews (1): Last reviewed commit: "Add CodSpeed performance benchmarks and ..." | Re-trigger Greptile

Comment on lines +321 to +328
def test_example_json_roundtrip(self, benchmark, nested_example):
"""Benchmark JSON serialization and deserialization of an Example."""

def roundtrip():
d = nested_example.toDict()
_ = json.dumps(d)

benchmark(roundtrip)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 The benchmark name, docstring, and inner function are all called "roundtrip", but json.loads is never called — only toDict() + json.dumps are measured. Anyone relying on this benchmark to evaluate the full serialization/deserialization cycle will see only half the picture; the deserialization cost (which can be comparable) is silently omitted.

Suggested change
def test_example_json_roundtrip(self, benchmark, nested_example):
"""Benchmark JSON serialization and deserialization of an Example."""
def roundtrip():
d = nested_example.toDict()
_ = json.dumps(d)
benchmark(roundtrip)
def test_example_json_roundtrip(self, benchmark, nested_example):
"""Benchmark JSON serialization and deserialization of an Example."""
def roundtrip():
d = nested_example.toDict()
s = json.dumps(d)
_ = json.loads(s)
benchmark(roundtrip)

Comment on lines +82 to +88
@pytest.fixture
def chat_completion_text(qa_signature_class):
return "[[ ## answer ## ]]\nParis is the capital of France.\n\n[[ ## completed ## ]]"


# ---------------------------------------------------------------------------
# Example / Prediction benchmarks

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 The chat_completion_text fixture is never referenced by any benchmark test, yet it declares qa_signature_class as a parameter — causing pytest to eagerly instantiate that fixture (a class with DSPy field metaclass machinery) on every test collection run, for no benefit. It should be removed.

Suggested change
@pytest.fixture
def chat_completion_text(qa_signature_class):
return "[[ ## answer ## ]]\nParis is the capital of France.\n\n[[ ## completed ## ]]"
# ---------------------------------------------------------------------------
# Example / Prediction benchmarks
# ---------------------------------------------------------------------------
# Example / Prediction benchmarks

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.

1 participant