DeepSeek Harness streams at 2fps inside gzip buffering

TroubleshootingPublished 2026-10-20Author: DeepSeek Plugin Market
DeepSeek HarnessDSHgzipZ_SYNC_FLUSHstreamingWebServer
DeepSeek Harness /api streams stutter at about 2fps because gzip holds chunks in zlib until the buffer fills. Three ways to flush or bypass compression.

A phone mirror or emulator preview shows about 2fps with the picture jumping once every two seconds, yet the device renders at 27fps and the host writes each packet within 90ms — the encoder is not the suspect, gzip is. With compression enabled, the DeepSeek Harness (DSH) web server also compresses application/octet-stream streams, and the middleware sets no flush policy, so zlib holds data until its buffer fills or the response ends (source).

Decide whether DeepSeek Harness is slow or batching

Static scenes are worst, server writes are evenly spaced, and client reads are evenly spaced — all three together mean batching, not encoder performance.

  1. Timestamp every server write. Expected: intervals match the device frame rate, around 30ms, not two seconds.
  2. Log each client reader.read() with its timestamp and byte count. Expected: reads about two seconds apart, each carrying a large chunk.
  3. Print the response headers and look for content-encoding: gzip. Expected: present, so the response was compressed.
  4. Disable compression temporarily and retest the same stream. Expected: the frame rate returns to the device level.
  5. Check whether a reverse proxy compresses again. Expected: no extra buffering or encoding header.
Look-alikeDistinguishing signal
Slow captureserver write intervals are long and uneven
Buffering middleboxno gzip header, but reads still arrive in batches
keep-alive reconnectthe stream stops outright and logs a rebuild
gzip batchingeven writes, gzip header, reads every two seconds

The measurements in the discussion match exactly: a static scene made the encoder emit one large keyframe every two seconds, and the buffer released once every two seconds, so the stream looked like a 2fps encoder (#10024).

Why the DeepSeek Harness middleware accumulates the stream

The filter excludes only text/event-stream, application/octet-stream passes through, and the configuration sets level and threshold but no flush.

  1. createGzipMiddleware lets content-range responses through and skips text/event-stream. Expected: everything else goes to the default filter.
  2. mime-db marks application/octet-stream as compressible, and unknown-length streams are explicitly allowed. Expected: the video stream enters the compressor.
  3. The compressor has no flush option, so zlib emits only when its buffer is full or the stream ends. Expected: batching appears.
  4. The HTTP bridge calls res.write(chunk) without res.flush(). Expected: data sits in zlib.
  5. Every frame waits until the next keyframe fills the buffer. Expected: the client updates at keyframe cadence.

One easy-to-miss premise: compression: 'gzip' is not the web server default. The default is none, and a bundle turns it on, which is why the problem appears on some installs and fuels the "it works on another machine" misdiagnosis.

A static scene is the worst case because no small packets feed the compressor; only a large enough keyframe triggers output. More motion means denser packets and less visible batching, the opposite of the "bad network" intuition.

Path 1: add Z_SYNC_FLUSH to DeepSeek Harness compression

One configuration line makes every write immediately visible and benefits every streaming route.

  1. Find where the web server constructs the compression middleware and confirm it passes only level and threshold. Expected: no flush field.
  2. Add flush: zlib.constants.Z_SYNC_FLUSH to the same object. Expected: the middleware constructs and startup is clean.
  3. Restart the host and retest the original route. Expected: the client reads each frame as it arrives.
  4. Watch bandwidth and CPU. Expected: a few extra bytes per chunk and roughly unchanged CPU.
  5. Roll it out on one profile first and keep a configuration backup. Expected: a one-command rollback exists.
  6. Remove the field and restart to roll back. Expected: behaviour matches the previous build.
  7. Retest a large file download. Expected: any compression-ratio change is acceptable.

With very small chunks, per-chunk flushing lowers the compression ratio. Merge flushes once per event-loop turn to trade a little throughput for latency.

Path 2: exclude the stream type from DeepSeek Harness compression

If the compressor should not touch streaming responses, skip that content type in the filter or give DSH plugin routes a "do not compress" marker.

  1. Declare a recognizable type for streaming routes instead of sharing application/octet-stream with binary downloads. Expected: the server can distinguish them.
  2. Return false for that type in filter. Expected: no content-encoding: gzip.
  3. Keep compression for JSON and static assets. Expected: assets stay gzipped and sizes do not regress.
  4. Regression-test a page load and a file download. Expected: both behave normally.
  5. Document the new type. Expected: the next plugin route can find the rule.
  6. Verify one streaming route from a plugin. Expected: data arrives at write cadence.

This contract is cleaner but needs a new plugin-facing entry point. Once the type name ships, do not rename it, or older plugins silently return to being compressed.

Path 3: flush from the DeepSeek Harness writer

When the host cannot change, plugins can flush themselves, because the compression middleware attaches flush to the response object.

  1. Grab the response object in the write loop and check whether flush exists. Expected: defined when compression is on.
  2. Call res.flush?.() after each res.write(chunk), then handle backpressure. Expected: client reads correspond one-to-one with writes.
  3. Write a composition test where the second chunk only arrives after the client decodes the first. Expected: it times out without the flush.
  4. Enable it only for streaming routes. Expected: ordinary large-file compression is unaffected.
  5. Log the flush count. Expected: it matches the number of writes.

No host change is needed, but every plugin must remember the call and the host default stays unfixed. The more plugins you run, the easier one is to miss.

Verify and set bounds for DeepSeek Harness streams

After the fix, measure time to first chunk and inter-chunk spacing, not average FPS, which keyframe cadence can mask.

  1. Record first-chunk time with compression on and off. Expected: with flush, both are the same order of magnitude.
  2. Confirm chunked transfer rather than a fixed Content-Length. Expected: no length header on the stream.
  3. Test a static scene. Expected: the worst input, and the one that exposes batching.
  4. Watch for five minutes. Expected: chunk spacing is steady, with no periodic stall.
PathChange siteCoverageMain cost
Path 1one host middleware settingevery streaming routeslightly lower ratio
Path 2host filter plus a route markerselected streaming routesneeds a plugin entry point
Path 3plugin write loopone pluginevery plugin must call it

Three limits apply: already-compressed payloads gain little, so do not expect a ratio win; a reverse proxy may compress again, so check it after changing the host; and no official fixed version appears in the discussion. For connection-layer stalls, see web performance and connection issues.

FAQ

Is DeepSeek Harness /api H.264 reaching only 2fps with a two-second jump because the encoder is slow?

DeepSeek Harness holds the response inside gzip, so the encoder is usually not the cause. The middleware sets no flush, so zlib waits until its buffer fills, and a static scene sends one large keyframe about every two seconds.

Why is the same plugin smooth on some DeepSeek Harness installs and stuck at 2fps on others?

DeepSeek Harness enables gzip only when a bundle turns it on, so the default stays none. With compression on, unknown-length streams such as application/octet-stream are compressed too; with it off, data passes through. Check the response header first.

Does adding Z_SYNC_FLUSH to DeepSeek Harness noticeably increase bandwidth or CPU?

DeepSeek Harness adds a few flush bytes per chunk, negligible at 16 to 64 KB. The main cost is a slightly lower compression ratio, and already-compressed payloads such as H.264 barely notice; merge flushes per event-loop turn if chunks are tiny.

Can a plugin or client fix DeepSeek Harness stream buffering without a host change?

DeepSeek Harness stream buffering can be fixed on the write side, because the middleware attaches a flush method to the response. Calling res.flush after each write pushes the chunk out without a host change, but every plugin must remember to call it.

Related Terms

Z_SYNC_FLUSH
Z_SYNC_FLUSH is a zlib flush mode that emits all compressed data so far while keeping the compression dictionary for later input. It suits streaming responses without ending the whole stream.— https://github.com/deepseek-ai/deepseek-harness/discussions/10024
compression middleware
The compression middleware is the wrapper the DeepSeek Harness web server uses for gzip, configurable by level, threshold, and filter. It selects by content type and length but does not flush zlib on the caller's behalf.— https://github.com/deepseek-ai/deepseek-harness/discussions/10024
chunked transfer
Chunked encoding is HTTP/1.1 transfer without a Content-Length, sending data as it is produced. Streaming endpoints typically use it, which also lets the compressor decide when to emit.— https://github.com/deepseek-ai/deepseek-harness/discussions/10024

Sources