pipedog is an unofficial community port of Pipecat, not affiliated with Daily.
pipedog

Performance

pipedog on Bun, Node, Deno and Cloudflare Workers against Pipecat on CPython, measured on the same machine with the same scenarios.

pipedog and Pipecat run the same pipelines: a frame goes through the same processors, the same queues and the same PipelineWorker. This page measures what that costs on each runtime, offline (the framework alone) and against Gemini (a real service, where the network dominates).

Every scenario is written twice, once against each framework, in the same shape: tools/bench/perf/pipedog.ts and tools/bench/perf/pipecat_bench.py. Pipecat runs from the upstream commit pipedog is ported from. Both log at WARNING. pipedog runs from a bundle made with bun build, as it is deployed. The runtimes take turns within each round, so a slow minute on the network or on the machine falls on all of them.

Results

Frame processing

10 pass-through processors in a PipelineWorker. Throughput: 20,000 TextFrames queued at once. Latency: one frame at a time, queued to arrival at the end of the pipeline. In brackets: compared with Pipecat (higher is better).

Throughput (frames/s)Latency per frame (median)p90Peak memory
Pipecat · CPython 3.13.139,294 (1.0×)572 µs (1.0×)578 µs144 MB
pipedog · Bun 1.4.244,394 (4.8×)52 µs (11.0×)60 µs184 MB
pipedog · Node 24.14.052,350 (5.6×)41 µs (13.8×)58 µs278 MB
pipedog · Deno 2.9.553,288 (5.7×)59 µs (9.7×)74 µs221 MB
pipedog · workerd 1.20261005.145,198 (4.9×)180 µs (3.2×)239 µs–

Cold start

From starting the process to a pipeline that has started and ended, with the Google services loaded (for workerd: from starting workerd serve to the response). Runtime alone is the same for an empty program, so the difference is what loading the framework and running the pipeline cost.

Cold startRuntime aloneFrameworkPeak memoryBundle
Pipecat · CPython396 ms13 ms383 ms114 MB–
pipedog · Bun44 ms6 ms38 ms51 MB3.5 MB
pipedog · Node66 ms21 ms45 ms87 MB3.8 MB
pipedog · Deno47 ms20 ms27 ms90 MB3.1 MB
pipedog · workerd42 ms11 ms31 ms–1.7 MB

Gemini LLM: time to first token

GoogleLLMService with gemini-3.5-flash-lite (thinking at its minimum, as the service sends by default), from queueing an LLMContextFrame to the first LLMTextFrame at the end of the pipeline. SDK alone is the same request made with the Google GenAI SDK of the same language, in the same process, alternating with the pipeline.

Through the pipeline (median)SDK alone (median)Difference, paired (median)Middle half of differencesRequests
Pipecat · CPython529 ms491 ms-6 ms-41 to 104 ms36
pipedog · Bun497 ms452 ms15 ms-24 to 116 ms36
pipedog · Node516 ms500 ms-21 ms-173 to 166 ms36
pipedog · Deno527 ms513 ms40 ms-30 to 93 ms36
pipedog · workerd496 ms467 ms51 ms-68 to 129 ms36

Gemini TTS: time to first audio

GeminiTTSService with gemini-3.8-flash-tts, from queueing a TTSSpeakFrame to the first TTSAudioRawFrame at the end of the pipeline, and the same request made with the SDK.

Through the pipeline (median)SDK alone (median)Difference, paired (median)Middle half of differencesRequests
Pipecat · CPython758 ms664 ms127 ms15 to 204 ms24
pipedog · Bun741 ms626 ms127 ms43 to 192 ms24
pipedog · Node753 ms727 ms30 ms-42 to 117 ms24
pipedog · Deno786 ms803 ms84 ms-126 to 253 ms24
pipedog · workerd876 ms642 ms170 ms92 to 294 ms24

Measured 2026-10-22 on Apple M4 Pro (12 cores, 24 GB), darwin 25.6.0: pipedog 0.8.0, Pipecat 5558d26dca (pinned upstream) on CPython 3.13.13, Bun 1.4.2, Node 24.14.0, Deno 2.9.5, workerd 1.20261005.1. 6 rounds, runtimes taking turns.

Reading the numbers

Frame processing is the framework alone: no I/O, no models. It's what a pipeline spends moving frames between processors, so it sets the floor under every response. In a voice bot each audio chunk (every 10–20 ms) and each LLM token goes through it.

Workers run pipedog's event loop the slowest of the JavaScript runtimes. Its timers and setImmediate wait about a millisecond, so @pipedog/asyncio yields through a body read instead, and a frame takes several such yields across 10 processors. That is still well under a millisecond per frame.

Peak memory in the JavaScript runtimes is mostly garbage the collector hasn't reclaimed yet, not memory in use. With 20,000 frames sent through, the live heap after a forced collection grows by about 5 MB on Node, and drops back once the pipeline ends. CPython frees objects as soon as the last reference goes, so its peak stays closer to what it holds. Under memory pressure V8 collects earlier; --max-old-space-size caps it.

Cold start matters for serverless (a Worker or a function that starts per call) and for scaling out. Pipecat's is mostly importing Python modules (here with the Google services). A bundle loads in one read.

Gemini times are dominated by the network and the model, which vary by hundreds of milliseconds from one request to the next. The SDK alone column makes the same request with Google's SDK, in the same process, right before or after each pipeline request. The paired difference is the framework's share, and is mostly within the noise for both frameworks. TTS services send audio downstream in chunks of a fixed size (as Pipecat does), so the first audio frame waits for the first full chunk, while the SDK column stops the clock at the first byte.

Telephony audio

On a phone call, the bot's speech goes through the same steps on every call: the text-to-speech audio (usually 24 kHz) is resampled to the provider's rate (8 kHz for Twilio, Telnyx and others), μ-law encoded, and sent over a WebSocket in small messages paced in real time. These steps decide what the caller hears, so they are measured directly against Pipecat's.

Resampling and μ-law

Pipecat resamples with libsoxr at its highest quality (VHQ). pipedog can't load native code on every runtime, so it uses a windowed-sinc resampler compiled to WebAssembly (@pipedog/resampler-wasm), with a pure-TypeScript fallback. Both get the same test signal: three tones they must keep (300 Hz, 1 kHz, 2.5 kHz) and three above 4 kHz that a resampler going down to 8 kHz must remove. μ-law is checked bit for bit against Pipecat's (Python's audioop).

ResamplingPipecat (libsoxr, VHQ)pipedog (WebAssembly)pipedog (TypeScript fallback)20 ms chunks vs one go: Pipecat / pipedog
24 kHz → 8 kHz-83 / -104 dB-88 / -98 dB-88 / -95 dB-80 dB / identical
24 kHz → 16 kHz-83 / -105 dB-87 / -100 dB-88 / -100 dB-81 dB / identical
16 kHz → 8 kHz-83 / -102 dB-87 / -100 dB-87 / -97 dB-80 dB / identical
8 kHz → 16 kHz-82 / -97 dB-86 / -98 dB-86 / -96 dB-80 dB / identical
24 kHz → 48 kHz-82 / -100 dB-85 / -99 dB-86 / -101 dB-81 dB / identical
48 kHz → 16 kHz-83 / -107 dB-87 / -100 dB-88 / -100 dB-81 dB / identical

Each cell: everything besides the tones (noise, aliasing, distortion) / the largest single artefact, relative to the tones. Lower is cleaner; both are far below what can be heard.

μ-law: all 65,536 16-bit values encoded: identical to Pipecat's; all 256 codes decoded: identical.

Pacing

A bot plays two 6-second utterances through the WebSocket server transport and the Telnyx serializer, and a separate process standing in for Telnyx records when each audio message arrives. The dropouts are what a playback buffer of that size would run out of: silences a caller would hear. Each case runs with the bot's event loop idle and kept busy by other work, standing in for VAD, other calls and LLM streaming. Both frameworks use the same pacing code; the message sizes differ because libsoxr's stream resampler returns audio in blocks, while pipedog's returns it sample by sample, so pipedog sends a small message about every 40 ms and Pipecat a larger one every 120–160 ms. Telephony providers buffer more than 40 ms, so neither drops audio in practice.

Event loop busyGap between messages, median / worstDropouts, 20 ms bufferDropouts, 40 ms bufferDropouts, 80 ms buffer
Pipecat · CPython0%121 / 163 ms8 (27 ms of silence)nonenone
pipedog · Bun0%40 / 48 msnonenonenone
pipedog · Node0%40 / 44 msnonenonenone
pipedog · Deno0%40 / 47 msnonenonenone
Pipecat · CPython40%122 / 163 ms8 (28 ms of silence)nonenone
pipedog · Bun40%40 / 60 msnonenonenone
pipedog · Node40%41 / 53 msnonenonenone
pipedog · Deno40%39 / 50 msnonenonenone
Pipecat · CPython70%125 / 167 ms7 (36 ms of silence)nonenone
pipedog · Bun70%41 / 43 msnonenonenone
pipedog · Node70%41 / 43 msnonenonenone
pipedog · Deno70%38 / 47 msnonenonenone

Running it

bun install
GOOGLE_API_KEY=... bun run bench:perf --write   # without a key, the Gemini scenarios are skipped
bun run bench:perf --runtime bun,python --scenario hop --rounds 1
bun run bench:audio --write    # resampling and μ-law against Pipecat's
bun run bench:pacing --write   # telephony pacing (--runtime, --load to narrow it down)

The first run creates a Python 3.13 environment with uv and installs Pipecat from the pinned upstream checkout (bun run upstream restores it). --write saves the raw samples to tools/bench/perf/results.json and the tables above. Model inference (Silero VAD, Smart Turn) has its own benchmark, bun run bench; its results are in the @pipedog/onnx README.

pipedog is an unofficial community TypeScript port of Pipecat. It is not affiliated with or endorsed by Daily or the Pipecat project.

Portions adapted from the Pipecat documentation, Copyright (c) 2024–2025 Daily, BSD-2-Clause (see apps/docs/LICENSE-pipecat-docs).

On this page