wave9 complete: 458/207 — airflow/argo/pytorch-0003/tf/jax/pyg/grpc/thrift + graphhopper/valhalla

This commit is contained in:
russell@unturf.com 2026-03-27 16:59:04 -04:00
parent 81bc62b9cb
commit ab10b2f555
22 changed files with 2889 additions and 16 deletions

View file

@ -0,0 +1,89 @@
# pytorch-0001 — graph_fuser.cpp fuseChunkByReusingExistingFusedChunk O(N²)
## Location
`torch/csrc/jit/passes/graph_fuser.cpp`, lines 513527
## Defective Code
```cpp
void fuseChunkByReusingExistingFusedChunk(
Node* group,
Node* chunk,
Node* existingFusedChunk) {
if (chunk->outputs().size() != existingFusedChunk->outputs().size()) {
return;
}
auto& subgraph = getSubgraph(group);
for (size_t i = 0; i < chunk->outputs().size(); ++i) { // O(C) outer loop
// Find the input to the FusionGroup (group)
auto* replacement_val = existingFusedChunk->outputs().at(i);
auto* val = chunk->outputs().at(i);
auto it = std::find(group->inputs().begin(), group->inputs().end(), val); // O(I) scan
auto input_index = it - group->inputs().begin();
// Rewrite the graph to use replacement_val
auto group_input = subgraph.inputs().at(input_index);
group_input->replaceAllUsesWith(replacement_val);
group->removeInput(input_index);
subgraph.eraseInput(input_index);
}
chunk->destroy();
}
```
The outer loop iterates over `chunk->outputs()` (size C = chunk count).
For each iteration, `std::find` performs a linear scan of `group->inputs()` (size I = fusion group inputs).
Total: **O(C × I)**.
In a large model with many fusion groups and chunk splits, C and I both grow with graph depth.
`fuseChunkByReusingExistingFusedChunk` is called from `canFuseChunk` which is itself called in a
scan loop over all nodes, making the aggregate complexity **O(N × C × I)**.
## Fixed Code
```cpp
void fuseChunkByReusingExistingFusedChunk(
Node* group,
Node* chunk,
Node* existingFusedChunk) {
if (chunk->outputs().size() != existingFusedChunk->outputs().size()) {
return;
}
auto& subgraph = getSubgraph(group);
// Build a reverse index: Value* -> input position, O(I) once
std::unordered_map<Value*, size_t> input_index_map;
const auto inputs = group->inputs();
for (size_t k = 0; k < inputs.size(); ++k) {
input_index_map[inputs[k]] = k;
}
// Process in reverse order so that removeInput(index) doesn't shift earlier indices
for (int i = static_cast<int>(chunk->outputs().size()) - 1; i >= 0; --i) {
auto* replacement_val = existingFusedChunk->outputs().at(i);
auto* val = chunk->outputs().at(i);
auto map_it = input_index_map.find(val); // O(1)
if (map_it == input_index_map.end()) continue;
size_t input_index = map_it->second;
auto group_input = subgraph.inputs().at(input_index);
group_input->replaceAllUsesWith(replacement_val);
group->removeInput(input_index);
subgraph.eraseInput(input_index);
}
chunk->destroy();
}
```
## Complexity Analysis
| Path | Complexity |
|------|-----------|
| Slow (original) | O(C × I) per call, O(N × C × I) aggregate |
| Fast (fixed) | O(I + C) per call, O(N × (I + C)) aggregate |
At C=32 chunks and I=128 group inputs: **32× speedup per call**.
## Severity
**HIGH** — executed in the kernel fusion pass over every fusion group during model compilation.
Large transformer models with `torch.compile()` fuse hundreds of groups with dozens of chunk splits.

View file

@ -0,0 +1,120 @@
# pytorch-0002 — graph_fuser.cpp mergeNodeIntoGroup + tryToMoveChunk O(N²)
## Location
`torch/csrc/jit/passes/graph_fuser.cpp`
- `mergeNodeIntoGroup`: lines 382391
- `tryToMoveChunk`: lines 762780
## Defective Code — mergeNodeIntoGroup
```cpp
// line 382
auto inputs = group->inputs();
for (size_t i = 0; i < n->outputs().size(); ++i) { // O(O) outer loop
auto it = std::find(inputs.begin(), inputs.end(), n->outputs()[i]); // O(I) each
if (it != inputs.end()) {
size_t p = it - inputs.begin();
group->removeInput(p);
subgraph.inputs()[p]->replaceAllUsesWith(in_graph->outputs()[i]);
subgraph.eraseInput(p);
}
}
```
## Defective Code — tryToMoveChunk
```cpp
// line 762
for (auto input : producer_for_chunk_node->inputs()) { // O(I) outer loop
if (!input->type()->isSubtypeOf(*TensorType::get()))
continue;
auto bchunk_inputs = bchunk->inputs();
auto it = std::find(bchunk_inputs.begin(), bchunk_inputs.end(), input); // O(B) each
if (it != bchunk_inputs.end()) {
chunked_inputs.emplace_back();
auto input_index = std::distance(bchunk_inputs.begin(), it);
for (const auto chunki : c10::irange(nchunks)) {
chunked_inputs.back().push_back(
bchunk->outputs().at(nchunks * input_index + chunki));
}
continue;
}
bchunk->addInput(input);
// ...
}
```
In `mergeNodeIntoGroup`: O(O × I) where O = outputs of merged node, I = fusion group inputs.
In `tryToMoveChunk`: O(I × B) where I = producer inputs, B = broadcast chunk inputs.
## Fixed Code — mergeNodeIntoGroup
```cpp
// Build reverse index once: O(I)
std::unordered_map<Value*, size_t> input_pos;
const auto inputs_vec = group->inputs().vec();
for (size_t k = 0; k < inputs_vec.size(); ++k) {
input_pos[inputs_vec[k]] = k;
}
// Erase in reverse to preserve indices
std::vector<size_t> to_erase;
for (size_t i = 0; i < n->outputs().size(); ++i) {
auto mit = input_pos.find(n->outputs()[i]); // O(1)
if (mit != input_pos.end()) {
subgraph.inputs()[mit->second]->replaceAllUsesWith(in_graph->outputs()[i]);
to_erase.push_back(mit->second);
}
}
std::sort(to_erase.rbegin(), to_erase.rend());
for (size_t p : to_erase) {
group->removeInput(p);
subgraph.eraseInput(p);
}
```
## Fixed Code — tryToMoveChunk
```cpp
// Build reverse index once: O(B)
std::unordered_map<Value*, size_t> bchunk_input_pos;
const auto bchunk_inputs_vec = bchunk->inputs().vec();
for (size_t k = 0; k < bchunk_inputs_vec.size(); ++k) {
bchunk_input_pos[bchunk_inputs_vec[k]] = k;
}
for (auto input : producer_for_chunk_node->inputs()) {
if (!input->type()->isSubtypeOf(*TensorType::get()))
continue;
auto mit = bchunk_input_pos.find(input); // O(1)
if (mit != bchunk_input_pos.end()) {
chunked_inputs.emplace_back();
auto input_index = mit->second;
for (const auto chunki : c10::irange(nchunks)) {
chunked_inputs.back().push_back(
bchunk->outputs().at(nchunks * input_index + chunki));
}
continue;
}
bchunk->addInput(input);
// ...
}
```
## Complexity Analysis
| Path | Complexity |
|------|-----------|
| Slow mergeNodeIntoGroup | O(O × I) |
| Fast mergeNodeIntoGroup | O(O + I) |
| Slow tryToMoveChunk | O(I × B) |
| Fast tryToMoveChunk | O(I + B) |
At O=16, I=128: 128× speedup in merge. At I=32, B=64: 64× speedup in chunk move.
## Severity
**HIGH** — both functions sit on the hot path of the JIT kernel fusion pass (`torch.compile`, `torch.jit.script`).
`mergeNodeIntoGroup` is called once per node during fusion graph construction.
`tryToMoveChunk` is called in the node scan loop for every FusionGroup consumer.

View file

@ -0,0 +1,62 @@
# pytorch-0003 — python_function.cpp tracer subgraph construction O(N²)
## Location
`torch/csrc/autograd/python_function.cpp`, lines 10191030
## Defective Code
```cpp
// line 1019
for (it++; it != owning_block->nodes().end(); ++it) { // O(N) outer loop over all nodes
torch::jit::Node* node = *it;
auto* clone_node =
subgraph->insertNode(subgraph->createClone(node, value_map_func));
for (size_t i = 0; i < node->outputs().size(); ++i) { // O(K) outputs per node
value_map[node->outputs()[i]] = clone_node->outputs()[i];
auto trace_it = std::find(
trace_outputs.begin(), trace_outputs.end(), node->outputs()[i]); // O(T) scan
if (trace_it != trace_outputs.end()) {
subgraph->registerOutput(clone_node->outputs()[i]);
}
}
}
```
`trace_outputs` is a `std::vector<Value*>`. For each of N nodes with K outputs,
`std::find` scans all T trace outputs: **O(N × K × T)**.
## Fixed Code
```cpp
// Build a set once before the loop: O(T)
std::unordered_set<Value*> trace_outputs_set(
trace_outputs.begin(), trace_outputs.end());
for (it++; it != owning_block->nodes().end(); ++it) {
torch::jit::Node* node = *it;
auto* clone_node =
subgraph->insertNode(subgraph->createClone(node, value_map_func));
for (size_t i = 0; i < node->outputs().size(); ++i) {
value_map[node->outputs()[i]] = clone_node->outputs()[i];
if (trace_outputs_set.count(node->outputs()[i])) { // O(1)
subgraph->registerOutput(clone_node->outputs()[i]);
}
}
}
```
## Complexity Analysis
| Path | Complexity |
|------|-----------|
| Slow (original) | O(N × K × T) |
| Fast (fixed) | O(T + N × K) |
At N=200 nodes, K=4 outputs/node, T=50 trace outputs: **50× speedup**.
## Severity
**MEDIUM** — executed during JIT tracing of Python autograd functions. Triggered whenever
`torch.jit.trace` or the tracing autograd path is used. T grows with the number of outputs
being traced; N grows with the function's graph depth.