Skip to content

fix: gzip trace uploads at level 1 - #1129

Merged
kcoopermiller merged 1 commit into
mainfrom
fix/traces-gzip-level
Nov 2, 2026
Merged

kcoopermiller merged 1 commit into
mainfrom
fix/traces-gzip-level

Conversation

@mikasenghaas

@mikasenghaas mikasenghaas commented Oct 29, 2026 •

Copy link
Copy Markdown
Member

Summary

  • Trace uploads gzip at level 1 instead of the default level 9 (GZIP_LEVEL in prime_traces.core.client, used by the sync and async clients).
  • The upload thread was CPU-bound on compression. With a fast producer, the prime_runs upload queue filled and dropped episodes after its 5 s put timeout.

Verification

One agentic episode: 100 turns, 100k tokens, 2.17 MB upload body.

Compression Time Ratio
gzip 9 (before) 71 ms 4.63
gzip 6 42 ms 4.61
gzip 3 16 ms 4.25
gzip 1 (after) 8 ms 4.09

Uploader cost per episode (TracesSink.write through a fake endpoint that gzips like the client): 78 ms before, 16 ms after.

prime-rl monitor load test (PrimeIntellect-ai/prime-rl#4317), 1000 such episodes logged as fast as possible through PrimeMonitor:

Before After
Episodes delivered 993 / 1000 (7 dropped: "Upload queue full after 5.0s") 1000 / 1000
Wall time 77 s 12 s
  • uv run pytest tests/ in packages/prime-traces: 183 passed.

🤖 Generated with Claude Code


Note

Low Risk
Transport-only gzip tuning with no change to content addressing or auth; slightly larger uploads in exchange for faster client-side compression.

Overview
Trace upload bodies are now gzipped at level 1 instead of Python’s default level 9, via a shared GZIP_LEVEL constant in client.py used by both sync and async upload_batch paths (still with mtime=0 for reproducible retries).

This targets upload throughput: compression was CPU-bound and could back up fast producers (e.g. upload queue timeouts and dropped episodes). Payload semantics are unchanged—idempotency and limits still apply to the uncompressed JSONL; only transport compression speed and wire size (~slightly larger) differ.

Reviewed by Cursor Bugbot for commit fce799d. Bugbot is set up for automated code reviews on this repo. Configure here.

The upload thread spent ~70 ms of its ~78 ms per 2 MB episode in
gzip at the default level 9, so it fell behind a fast producer and
the upload queue dropped episodes. Level 1 is ~9x faster for ~13%
more bytes on the wire.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 29, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-29T22:17:46.868290Z fce799d PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

hallerite pushed a commit to PrimeIntellect-ai/prime-rl that referenced this pull request Nov 2, 2026
## Summary

Part of the monitors stack: #4228 → #4316 → **#4317** → #4244. Stacked
on #4316.

- Adds `tests/unit/monitors/test_monitor_load.py`: one test,
parametrized over monitor (file, prime, W&B) × episode count (10, 100,
1000).
- Each episode is agentic and heavy: 100 turns, 100k tokens, numpy
streams as the env-server wire delivers them (about 2 MB per upload).
  - Interleaved with 2000 metric rows of 200 keys.
- The episodes share one trace object. Every monitor serializes each
episode separately, so the load is the same at a fraction of the memory.
- The test logs through `monitors.log` as the orchestrator does, under
the orchestrator's `GarbageCollector` (no automatic full collections).
An asyncio ticker measures how late the event loop wakes.
- The test checks that everything logged arrives:
- **File monitor:** every episode in the trace stream, and steps 1 to
2000 in `metrics.jsonl`.
- **Prime monitor:** the real SDK run and uploader threads against fake
Prime Traces and platform endpoints. The fake traces endpoint gzips like
the real client.
- **W&B monitor:** the real SDK in offline mode. W&B takes metrics only.
- The test fails when a monitor stalls the loop: p99 lag of 50 ms or
more, max lag of 250 ms or more, or one `log()` call of 250 ms or more.
- `prime-1000` is an expected failure (strict). The SDK uploader gzips
each episode at level 9, about 70 ms here, so 1000 episodes logged at
once fill its queue. `log()` then waits for room for up to 5 s on the
orchestrator's arrival path, and the SDK drops what stays queued. A TODO
in the test tracks the fix; PrimeIntellect-ai/prime#1129 is the first
part (gzip level 1).
- The test is CPU-only, needs no network and is marked `slow`. CI's unit
job runs it (it deselects only `gpu`). It takes about 110 s.

## Verification

`pytest -s tests/unit/monitors/test_monitor_load.py` on the dev box,
with main's `prime-traces` pin: 8 passed, 1 xfailed.

| Monitor | Episodes | Loop lag p99 | Loop lag max | `log()` p99 |
`log()` max |
|---|---|---|---|---|---|
| File | 10 / 100 / 1000 | 3.6 / 3.4 / 4.7 ms | 3.7 / 5.6 / 4.9 ms | 0.1
/ 5.0 / 5.3 ms | 7.9 / 9.9 / 7.8 ms |
| Prime | 10 / 100 | 5.2 / 7.9 ms | 6.0 / 10.5 ms | 0.4 / 0.4 ms | 10.1
/ 15.4 ms |
| Prime | 1000 | xfail: 7 of 1000 episodes dropped ("Upload queue full
after 5.0s"), one `log()` call blocked 5 s | | | |
| W&B (offline) | 10 / 100 / 1000 | 1.6 / 1.3 / 1.1 ms | 6.0 / 1.6 / 1.4
ms | 0.6 / 0.5 / 0.6 ms | 6.1 / 0.8 / 0.8 ms |

Without the orchestrator's GC setup, a full collection pauses the loop
for 220–260 ms in the 1000-episode cases.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Low Risk**
> Test-only change with no production code paths modified; it exercises
existing monitor integrations under load.
> 
> **Overview**
> Adds **`tests/unit/monitors/test_monitor_load.py`**, a slow unit test
that drives **file**, **Prime**, and **W&B (offline)** monitors through
**`monitors.log`** the way the orchestrator does—heavy agentic episodes
(up to 1000 × 100 turns / ~100k tokens) interleaved with **2000** wide
metric rows, under the orchestrator **`GarbageCollector`** fixture.
> 
> For each monitor × episode count (**10 / 100 / 1000**), it asserts
**complete delivery** (trace chunks / fake Prime endpoints / W&B metric
steps) and **event-loop responsiveness** (p99 lag **&lt; 50 ms**, max
lag and single **`log()`** **&lt; 250 ms**). **Prime @ 1000 episodes**
is **`xfail` (strict)** documenting **`prime_runs`** upload-queue
backpressure that can block **`log()`** for seconds and drop episodes.
> 
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
bb9dbad. Bugbot is set up for automated
code reviews on this repo. Configure
[here](https://www.cursor.com/dashboard/bugbot).</sup>
<!-- /CURSOR_SUMMARY -->

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>

@kcoopermiller kcoopermiller left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Approving. Correct on both clients, and the digest and limits stay over uncompressed bytes. On real long traces I measured about 5x faster gzip for about 20% more bytes, which is the right trade for hosted training.

Two follow-ups, not blockers:

  • The prime-rl load test's fake hard-codes gzip level 9 and never calls the real client, so it can't see this change.
  • Drops still need checking with a real 1000-episode burst against Prime Traces. The queue and the 5 s blocking put are unchanged.

@kcoopermiller
kcoopermiller merged commit f6ce0c5 into main Nov 2, 2026
27 checks passed
@kcoopermiller
kcoopermiller deleted the fix/traces-gzip-level branch November 2, 2026 18:35
kcoopermiller added a commit that referenced this pull request Nov 2, 2026
Ships gzip level 1 (#1129), non-blocking concurrent uploads (#1136) and
self-encoded episode uploads (#1140).

prime-runs now requires prime-traces>=0.0.12: its traces sink can hand
TracesClient encoded lines, which 0.0.11 cannot take.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
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.

2 participants