Package
lexical-graph
Problem statement
extract() fans documents out to N workers, then pulls everything back through a single generator chain in the parent (lexical_graph_index.py:498 and :503):
nodes | extraction_pipeline | Pipe(handler.accept) | build_pipeline | sink
Pipe is plain generator composition with no threads, so extraction scales with workers but every document afterwards passes through one stage, one at a time. Eight times the workers returns 1.58x, converging near 33 docs/min.
At 1M documents that works out to roughly 21 days no matter how large the fleet, against a Bedrock quota floor of about 1.5 days.
Evidence
Two runs sit behind this. The 33 docs/min ceiling comes from the worker sweep: 2,000 documents, 2/4/8/16 workers at 16 threads each, on-demand, ml.m5.12xlarge. The rows below come from the single-worker runs: 500 documents, one worker, on-demand, ml.m5.4xlarge. Each run splits into a model phase and a drain phase, and the drain costs about 1.4 s per document.
| Variable changed |
Drain |
docs/min |
| 16 / 32 / 64 threads |
13.10 / 11.43 / 11.72 min |
38 / 44 / 43 |
| local disk instead of S3 |
14.1 min |
35.5 |
| Nova 2 Lite instead of Sonnet 4.6 |
12.7 min |
39.4 |
Removing S3 made it slower, which rules out upload cost. And the drain hardly moves between two models whose extraction phases differ by 2.8x.
Four candidates sit in that stage, none of them timed separately: extraction_monitor (:496, present only when a progress monitor is passed), Pipe(handler.accept), build_pipeline (created with num_workers=1 hardcoded at :490 and running only a NullBuilder), and sink.
Proposed solution
Time the four stages first, which is about ten lines. Two earlier attempts to fix this by reasoning from the code targeted the wrong component. One raised the uploader's effective thread count from the default of 4 to 64 and measured 1.15x end to end, which is real but leaves the drain untouched.
Then, in increasing order of effort:
- Raise
num_workers on the build pipeline (:490). One line, resting on the untested assumption that it's free.
- Give the handler stage a bounded reorder window. It has to yield in order and only once writes are durable.
- Remove the funnel entirely so workers write their own output rather than returning documents to the parent. This is the only option that scales with worker count.
Single run per configuration. The 13.10 / 11.43 / 11.72 thread row is a 13% spread with no repeat, so read it as "does not improve" rather than as a flat line. The local-disk and cross-model rows are the stronger evidence: both change the drain by less than the upstream phase they alter.
Package
lexical-graph
Problem statement
extract()fans documents out to N workers, then pulls everything back through a single generator chain in the parent (lexical_graph_index.py:498and:503):Pipeis plain generator composition with no threads, so extraction scales with workers but every document afterwards passes through one stage, one at a time. Eight times the workers returns 1.58x, converging near 33 docs/min.At 1M documents that works out to roughly 21 days no matter how large the fleet, against a Bedrock quota floor of about 1.5 days.
Evidence
Two runs sit behind this. The 33 docs/min ceiling comes from the worker sweep: 2,000 documents, 2/4/8/16 workers at 16 threads each, on-demand,
ml.m5.12xlarge. The rows below come from the single-worker runs: 500 documents, one worker, on-demand,ml.m5.4xlarge. Each run splits into a model phase and a drain phase, and the drain costs about 1.4 s per document.Removing S3 made it slower, which rules out upload cost. And the drain hardly moves between two models whose extraction phases differ by 2.8x.
Four candidates sit in that stage, none of them timed separately:
extraction_monitor(:496, present only when a progress monitor is passed),Pipe(handler.accept),build_pipeline(created withnum_workers=1hardcoded at:490and running only aNullBuilder), andsink.Proposed solution
Time the four stages first, which is about ten lines. Two earlier attempts to fix this by reasoning from the code targeted the wrong component. One raised the uploader's effective thread count from the default of 4 to 64 and measured 1.15x end to end, which is real but leaves the drain untouched.
Then, in increasing order of effort:
num_workerson the build pipeline (:490). One line, resting on the untested assumption that it's free.Single run per configuration. The 13.10 / 11.43 / 11.72 thread row is a 13% spread with no repeat, so read it as "does not improve" rather than as a flat line. The local-disk and cross-model rows are the stronger evidence: both change the drain by less than the upstream phase they alter.