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,189 @@
package unit;
import java.util.*;
/**
* Unit test for pytorch-0001 and pytorch-0002:
* graph_fuser.cpp fuseChunkByReusingExistingFusedChunk and tryToMoveChunk
* O(C×I) std::find on group inputs inside chunk output loop O(C+I) with HashMap.
*
* Compile: javac -d . PyTorchGraphFuserAlgorithm.java
* Run: java -ea unit.PyTorchGraphFuserAlgorithm
*/
public class PyTorchGraphFuserAlgorithm {
static int opCount = 0;
static void check(String desc, boolean cond) {
if (!cond) throw new AssertionError("FAIL: " + desc);
System.out.println("PASS: " + desc);
}
// -----------------------------------------------------------------------
// Simulated Value pointer identity by object reference
// -----------------------------------------------------------------------
static class Value {
final int id;
Value(int id) { this.id = id; }
@Override public String toString() { return "v" + id; }
}
// -----------------------------------------------------------------------
// SLOW: fuseChunkByReusingExistingFusedChunk std::find equivalent
// -----------------------------------------------------------------------
static int slowFuseChunk(List<Value> groupInputs, List<Value> chunkOutputs) {
int ops = 0;
for (Value val : chunkOutputs) { // O(C) outer
for (Value gi : groupInputs) { // O(I) inner = std::find
ops++;
if (gi == val) break;
}
}
return ops;
}
// -----------------------------------------------------------------------
// FAST: fuseChunkByReusingExistingFusedChunk HashMap lookup
// -----------------------------------------------------------------------
static int fastFuseChunk(List<Value> groupInputs, List<Value> chunkOutputs) {
int ops = 0;
// Build reverse index once: O(I)
Map<Value, Integer> indexMap = new HashMap<>();
for (int k = 0; k < groupInputs.size(); k++) {
indexMap.put(groupInputs.get(k), k);
ops++;
}
// Lookup per chunk output: O(1) each
for (Value val : chunkOutputs) {
ops++; // one lookup
indexMap.get(val); // O(1)
}
return ops;
}
// -----------------------------------------------------------------------
// SLOW: tryToMoveChunk std::find on bchunk_inputs per producer input
// -----------------------------------------------------------------------
static int slowTryMoveChunk(List<Value> producerInputs, List<Value> bchunkInputs) {
int ops = 0;
for (Value input : producerInputs) { // O(I) outer
for (Value bi : bchunkInputs) { // O(B) inner = std::find
ops++;
if (bi == input) break;
}
}
return ops;
}
// -----------------------------------------------------------------------
// FAST: tryToMoveChunk HashMap on bchunk_inputs
// -----------------------------------------------------------------------
static int fastTryMoveChunk(List<Value> producerInputs, List<Value> bchunkInputs) {
int ops = 0;
// Build index once: O(B)
Map<Value, Integer> indexMap = new HashMap<>();
for (int k = 0; k < bchunkInputs.size(); k++) {
indexMap.put(bchunkInputs.get(k), k);
ops++;
}
// Lookup per producer input: O(1) each
for (Value input : producerInputs) {
ops++;
indexMap.get(input); // O(1)
}
return ops;
}
// -----------------------------------------------------------------------
// Build test data some chunk outputs are in groupInputs, some are not
// -----------------------------------------------------------------------
static List<Value> makeValues(int n) {
List<Value> vs = new ArrayList<>();
for (int i = 0; i < n; i++) vs.add(new Value(i));
return vs;
}
public static void main(String[] args) {
System.out.println("=== PyTorchGraphFuserAlgorithm ===");
// --- pytorch-0001: fuseChunkByReusingExistingFusedChunk ---
// Worst case: chunk outputs are NOT in groupInputs slow scans full I list each time.
for (int N : new int[]{50, 100, 200, 500}) {
int C = N; // chunk outputs
int I = N; // group inputs (same size)
// groupInputs: values 0..I-1
List<Value> groupInputs = makeValues(I);
// chunkOutputs: fresh values NOT in groupInputs (ids I..I+C-1)
List<Value> chunkOutputs = new ArrayList<>();
List<Value> allVals = makeValues(I + C);
for (int i = I; i < I + C; i++) chunkOutputs.add(allVals.get(i));
int slowOps = slowFuseChunk(groupInputs, chunkOutputs);
int fastOps = fastFuseChunk(groupInputs, chunkOutputs);
// Slow: every chunk output misses, scans all I entries C * I
int expectedSlowMin = C * I - 1;
// Fast: I + C (build index + lookups)
int expectedFastMax = I + C + 1;
double ratio = (double) slowOps / fastOps;
check(String.format("pytorch-0001 N=%d: slow ops >= C*I=%d (got %d)", N, C * I, slowOps),
slowOps >= expectedSlowMin);
check(String.format("pytorch-0001 N=%d: fast ops <= I+C=%d (got %d)", N, I + C, fastOps),
fastOps <= expectedFastMax);
check(String.format("pytorch-0001 N=%d: ratio >= 10x (got %.1fx)", N, ratio),
ratio >= 10.0);
}
// --- pytorch-0002: tryToMoveChunk ---
// Worst case: producer inputs are NOT in bchunkInputs each scan misses (full B scan).
for (int N : new int[]{50, 100, 200, 500}) {
int I = N; // producer inputs
int B = N; // bchunk inputs
// producerInputs: ids 0..I-1; bchunkInputs: ids I..I+B-1 (no overlap full miss)
List<Value> allVals = makeValues(I + B);
List<Value> producerInputs = new ArrayList<>(allVals.subList(0, I));
List<Value> bchunkInputs = new ArrayList<>(allVals.subList(I, I + B));
int slowOps = slowTryMoveChunk(producerInputs, bchunkInputs);
int fastOps = fastTryMoveChunk(producerInputs, bchunkInputs);
// All I producer inputs miss fully I * B ops
int expectedSlowMin = I * B - 1;
int expectedFastMax = I + B + 1;
double ratio = (double) slowOps / fastOps;
check(String.format("pytorch-0002 N=%d: slow ops >= I*B=%d (got %d)", N, I * B, slowOps),
slowOps >= expectedSlowMin);
check(String.format("pytorch-0002 N=%d: fast ops <= I+B=%d (got %d)", N, I + B, fastOps),
fastOps <= expectedFastMax);
check(String.format("pytorch-0002 N=%d: ratio >= 10x (got %.1fx)", N, ratio),
ratio >= 10.0);
}
// --- pytorch-0001 correctness check ---
{
List<Value> vals = makeValues(10);
List<Value> groupInputs = new ArrayList<>(vals);
// chunk outputs = vals[2], vals[5], vals[8]
List<Value> chunkOutputs = Arrays.asList(vals.get(2), vals.get(5), vals.get(8));
// Slow and fast should agree on which indices are found
Map<Value, Integer> fastMap = new HashMap<>();
for (int k = 0; k < groupInputs.size(); k++) fastMap.put(groupInputs.get(k), k);
for (Value v : chunkOutputs) {
int slowIdx = groupInputs.indexOf(v);
int fastIdx = fastMap.getOrDefault(v, -1);
check(String.format("pytorch-0001 correctness: val %s slow=%d fast=%d", v, slowIdx, fastIdx),
slowIdx == fastIdx);
}
}
System.out.println();
System.out.println("27/27 PASS");
}
}