← gpu-pilot · json · source: REVIEW.md

External Design Review

Independent PhD-grade review: what's solid, what was wrong, what the product still needs. · updated 2026-06-10 19:08

TL;DR

  • Verdict: GPU transit-time design 7.5/10, but monetary-cost design 2/10 — earliest-arrival alone can never price a trip.
  • Caught real errata (all verified): memory math (14.25 B/stop-origin), 12.85 GB snapshot bug, cp.nonzero OOM, u16 silent clamp at 18.2 h.
  • Biggest architectural point: don't recompute transit per hex — factor into sparse access + reusable stop-to-stop profiles + egress.
  • Demanded mode/asset state (no teleporting cars), backward arrive-by search, time-based access limits instead of hex rings.
  • Its 5-tier test plan and pre-mortem table now drive the validation roadmap.

External Design Review — GPU RAPTOR + Multimodal Time/Cost Product

Date: 2026-06-10. Independent review (external reasoning model, transit-routing PhD framing) of DESIGN.md + kernel skeletons, commissioned by Krish. Transcribed from chat; math spot-checked on geo before filing (all numeric corrections verified — see §4).

Scope reviewed: the GPU RAPTOR pilot (this directory) and the larger product question it serves: "how long and how much from Point A to Point B," multimodal (walk / bike / drive / ride-hail / transit), H3 res-9 indexed, with monetary costs (fares, gas via EPA MPG, kWh, tolls, parking).


1. Verdict

The GPU RAPTOR kernel is a credible pilot, but the current design is not yet a complete or reliable solution to "how long and how much from A to B."

Dimension Rating
Transit-time GPU design 7.5 / 10
End-to-end multimodal time design 5 / 10
End-to-end monetary cost design 2 / 10
Scalability as currently proposed 4 / 10

The design correctly recognizes that timetable routing is structurally different from road routing. But it currently computes earliest arrival only, for one departure time — it does not preserve the itinerary needed to calculate fares, tolls, parking, fuel, electricity, ride-hail, or vehicle continuity. And it has not been compiled or executed.

2. What is solid (keep)

  1. RAPTOR instead of GPU A* for the schedule — right direction; OTP itself moved transit to RAPTOR. The real architectural choice is not "GPU vs OTP graph"; it is RAPTOR for transit + street-network routing for access/egress/transfers + a composition layer.
  2. Struct-of-arrays, stop-major/origin-minor (tau[stop][origin], warp = 32 origins) — coalesced label reads. Also good: int32 GTFS times with no modulo-24h, pattern grouping (not route_id), dual schedule layouts, CPU exact reference, synthetic tests (overtaking, >24:00), platform-level search, optimization postponed until exact CPU/GPU equality.
  3. Precomputed street access instead of crow-fly — haversine×1.35 is unreliable exactly where it matters (rivers, rail yards, highway trenches, station entrances).
  4. H3 as spatial index and output grid — res-9 (~0.105 km² avg cell, ~174 m avg edge) is reasonable for neighborhood-level display. But H3 is the index and display layer, not the routing graph.

3. The most important architectural change

Do not recompute the transit network independently for every H3 cell unless benchmarks prove it necessary. The transit system does not depend on the origin hex; the hex only affects access time to a small set of boarding stops. The clean factorization:

T(o, d, t) = min over (s, e) of [ A(o,s) + R(s, e, t + A(o,s)) + E(e,d) ]
  • A(o,s) — walk / bike / ride-hail / kiss-and-ride / park-and-ride access, origin cell → boarding stop (sparse table)
  • R(s,e,τ) — transit arrival profile, stop → stop, ready-to-board at τ
  • E(e,d) — egress, alighting stop → destination cell (sparse table)

For the ~1,000-stop subway pilot, a stop-to-stop time profile is dramatically smaller than a 535,471-origin × 1,000-stop matrix per departure sample. rRAPTOR exists precisely for departure-time-range / profile queries. At tri-state scale (~50K stops) full all-pairs profiles also blow up — there use regional partitioning, on-demand profiles, origin batching by shared access-stop sets, ULTRA transfer shortcuts, sparse reachability limits, hub profiles. (ULTRA = RAPTOR-like transit core + unrestricted multimodal transfer graphs without a transitively closed footpath graph.)

The existing origin×stop matrix is much better than hex-to-hex, but it may still repeat substantial transit computation unnecessarily.

4. Specific corrections to the GPU design (all verified)

4.1 Memory-math errors in DESIGN.md

Listed per-(stop,origin) state: 3 × i32 τ (12 B) + u16 output (2 B) + 2 mark bits (0.25 B) = 14.25 B, so: - 14.25 KB per origin at S=1,000 — the doc's "≈16.3 KB" double-counts travel_u16 as i32. (Total-state table at 7.6 GB was already consistent with 14.25 B.) - 14.25 B × 535,488 × 1,000 ≈ 7.63 GB state ✓ - At S=50,000: 712.5 KB/origin → with 85% of 13.2 GB free, ≈15K origins/chunk, not 21K (before graph, temporaries, output).

4.2 Snapshot memory understated

One i32 stop×origin slab = 2.14 GB. Rounds 0–5 resident = 6 × 2.14 = 12.85 GB, not +6.4 GB — does not fit beside the 7.6 GB state in 13.2 GB. (6.4 GB is only right if each snapshot is finalized to u16 first.) → Stream each round's slab to host instead of keeping snapshots resident.

4.3 cp.nonzero() can OOM at export

stop_i, origin_i = cp.nonzero(travel != UNREACH) over up to 535M reachable cells allocates multi-GB index arrays on top of the 7.6 GB state + CuPy temporaries + output frame. → Tiled compaction, int32 coords, prefix-sum compaction kernel, partitioned output (by origin chunk or stop range), streamed D2H buffers. Never build the full long-format row index in one op.

4.4 u16 silently clamps long trips

65,534 s ≈ 18.2 h. Acceptable for an NYC commuter pilot; unsafe for tri-state/intercity/overnight. The finalize kernel currently turns every longer finite journey into the same saturated value, silently. → u32 seconds, or u16 minutes, or a saturation bit, or an explicit product max-travel-time with rejection. Decide before scaling.

4.5 Zero boarding slack is not production-ready

RAPTOR_BOARD_SLACK_SEC = 0 creates mathematically-possible, operationally-unrealistic transfers. Needed eventually: station-specific transfer times, entrance/platform pathways, accessibility paths, agency buffers, same-vehicle vs change-vehicle distinction, guaranteed/forbidden transfers (GTFS pathways.txt, transfers.txt, pickup/drop-off rules, frequencies.txt, trip-to-trip transfer semantics).

4.6 Don't auto-connect all platforms under one parent station

"Same parent" ≠ valid quick transfer: separate paid areas, long mezzanines, no accessible connection, street exit + re-entry, direction-specific platforms, closed entrances, fare implications. Use pathways.txt + conservative fallbacks.

4.7 "Best-hopping" optimization is dangerous

Boarding from tau_best while skipping the per-round copy is not proven to converge to the same answer: labels improved in the current round can become boardable by another route in the same round → effectively two trips in one round, breaking transfer-count semantics, nondeterministic under kernel execution order. Keep strict tau_prev → tau_curr; do not ship best-hopping on empirical matching alone.

4.8 In-place transfer marking should be deterministic

Reading and extending marked in the same relax kernel is only safe if the closure holds shortest valid times, chained transfers are legally interchangeable with shortcuts, no route/paid-area/accessibility-dependent restrictions, and no intermediate state changes eligibility. Safer v1: trip_marked_snapshot → transfer relaxation → next_round_marked with separate input and output masks. Correctness is worth the extra bitmap.

4.9 Transitive closure won't scale to the regional network

Fine for 1,000 subway platforms with bounded transfers. At 50K stops with walk/bike/drive transfers, closure grows severely and encodes unrealistic chains. → bounded street searches, ULTRA shortcuts, early pruning, mode-specific transfer graphs, separate station-internal pathways from public-street transfers. (Recent work flags transfer relaxation as the main RAPTOR-family bottleneck.)

4.10 Performance estimate is a hypothesis, not a plan

RTX 5080 peak = 960 GB/s; the design assumes ~850 GB/s (~89% of peak) effective on a kernel with gathered per-lane trip reads, binary searches, divergence, atomics, sparse marking. Do not assume near-peak. Report kernel-only, access-matrix load, init, D2H, compaction, Parquet write, per-sample, and full-window times separately.

4.11 Baseline is not apples-to-apples

"15 OTP Java threads, 10 days" includes HTTP/GraphQL overhead, street searches, object construction, itinerary generation, serialization, polar sampling, duplicated transit work. Compare three implementations on identical output: (1) OTP API pipeline, (2) in-process optimized CPU RAPTOR over the same arrays, (3) GPU RAPTOR end-to-end. The win must be shown to come from the GPU, not from deleting OTP API overhead.

5. Critical product-level gaps

5.1 Earliest arrival does not give you cost

From (origin, stop, earliest_arrival) you cannot reconstruct: agencies used, route/trip boarded, fare transfer rules, caps, tolls crossed, parking facility/duration, road distance for fuel/kWh, or whether the leg was Uber vs own car vs kiss-and-ride. GTFS Fares v2 prices fare products applied to legs with directional transfer rules — it needs an itinerary, not an arrival time.

Fix — two-stage system: - Stage 1, journey generation: fastest / Pareto-relevant journeys with arrival time, boarding count, access mode, parent pointers or compact journey IDs. - Stage 2, exact cost evaluation: reconstruct the best ~5–20 nondominated itineraries; price transit fare, tolls, parking, ride-hail (quote or labeled estimate), gasoline/electricity, optional maintenance+depreciation. Do not put monetary fields in dense GPU labels (memory + Pareto-label blowup).

5.2 Independent access modes at A and B create impossible trips

Drive-to-station at A + drive-from-station at B = the car teleports. Mode/asset state is required at composition:

Mode state Valid behavior
Walk available both ends
Personal bike continues only if bike carriage permitted, or parked
Personal car / park-and-ride car stays at boarding station; unavailable at destination
Kiss-and-ride driver departs; no parking; include drop-off dwell
Ride-hail independently hireable at either end
Car-share station availability + one-way rules

5.3 "Reverse from B" is not a transpose

Transit is time-dependent and directional. Depart-at ⇒ forward RAPTOR; arrive-by ⇒ backward RAPTOR over a correctly reversed timetable. Forward-matrix transposition fails on departure times, waiting, one-ways, turn restrictions, directional service, directional fares. Reverse-from-B is fine for building the egress-stop set only.

5.4 Fixed H3-ring mode rules are not defensible

"Walk 1 hex, bike 2 hexes, then drive" fails: adjacent-cell distance varies, cells straddle rivers/freeways/yards, centroids can be inaccessible, adjacent cells can require long detours, one cell can hold several entrances with very different access. → time-based network limits (walk 5/10/15/20 min; bike 10/20/30; P&R = road time + parking search + station entry; K&R = road time + dwell; ride-hail = road time + pickup wait). H3 rings remain useful as a cheap geometric prefilter before street routing.

5.5 Full cell-to-cell brute force is not viable

535,471² ≈ 286.7 billion OD pairs ≈ 573 GB at 2 B/result for ONE departure sample, before metadata/modes/samples. → Never materialize hex-to-hex. Store sparse H3→stop access + stop→H3 egress + compressed stop profiles; compose at query time or per requested isochrone.

  1. Spatial + street network. H3 res-9 for joining/caching/display/aggregation. Per cell, one or more realistic street connectors (h3_id, mode, direction, street_vertex, connector_time, connector_distance, quality_flag) — nearest walkable node / address-weighted point / boundary portals / contained station entrances, not always the centroid. Separate sparse access tables per mode and direction (walk, bike, P&R, K&R, ride-hail).
  2. Timetable engine. Strict RAPTOR rounds, platform-level stops, bounded transfers, parent pointers for selected results, forward depart-at + separate backward arrive-by, calendar + exceptions, frequencies.txt, pickup/drop-off restrictions, block/in-seat continuation; GTFS-RT overlay later (static output labeled scheduled, with provenance timestamps).
  3. Journey + cost engine. Transit: Fares v2 products, zones, transfer windows, agency-to-agency rules, peak/off-peak, caps, rider category. Gas: route_miles / adjusted_MPG × local_price — FuelEconomy.gov/EPA vehicle DB; prefer city/hwy mix + speed profile + hybrid behavior + temperature + user-observed MPG over combined MPG. EV: route_miles × kWh_per_mile × charging_loss × $/kWh (kWh/100mi + MPGe from DOE data); separate home/work/L2/DCFC pricing; EIA APIs for gas + electricity prices. Tolls/congestion/parking: facility-operator or licensed data, not OSM (OSM lacks reliable current prices). Ride-hail: live authorized API quote, or clearly-labeled estimate range — never present a static mileage formula as a fare. Report two cost figures: cash cost vs fully-allocated vehicle cost (+ maintenance, tires, depreciation).
  4. Query composition. A→B: map both ends to H3 + connectors → small candidate access/egress sets per allowed mode → run/query the transit profile with actual access arrival times → preserve mode/asset state → add direct walk/bike/drive/ride-hail alternatives → reconstruct top journeys → exact costs → return fastest / cheapest / recommended-generalized-cost. Isochrone: transit arrivals at stops → expand sparse egress → threshold → per-cell min → polygonize for display only.

7. Pre-mortem (assume failure at month 6)

Failure Earliest warning sign Prevention
Fast but monetarily wrong results two itineraries, same arrival, same cost despite different agencies/tolls/parking preserve itinerary IDs; separate cost engine
Impossible multimodal journeys personal car on both ends of a transit trip mode + asset state
OOM during export kernel completes, cp.nonzero/cuDF fails tiled, streamed compaction/output
H3 access wrong near rivers/highways large unexplained discontinuities between adjacent hexes street-network routing, entrances, multiple connectors
Regional transfer graph explodes transfer CSR dominates memory/runtime bounded transfers or ULTRA shortcuts
GPU advantage disappears end-to-end kernel fast, serialization/output dominates benchmark full pipeline; redesign output path
Static schedules wrong in operation cancellations/changes absent GTFS-RT + provenance timestamps
Fare edge cases fail free transfers / caps / peak fares wrong per-agency fare test suites, itinerary-based eval
Fixed 5 rounds misses journeys sparse suburban trips unreachable time cap + configurable transfer cap
CPU and GPU agree but both wrong exact tests pass, OTP/known trips disagree independent oracle not sharing preprocessing

8. Expanded testing plan

  • Tier 1 — algorithm microtests: overtaking; >24:00 trips; same-vehicle continuation; forbidden pickup/drop-off; min transfer time; missed-transfer-by-one-second; frequency-based trip; one-way walking transfer; accessible vs inaccessible path; direct walk beats transit; P&R vehicle continuity; saturated u16 duration.
  • Tier 2 — exact differential CPU vs GPU: ≥10,000 randomized origins (not 1,000); multiple departure times; weekday/weekend/holiday; every round independently; random transfer limits; worst-case hub origins; origins with no nearby stop. (Catches GPU bugs, not preprocessing bugs.)
  • Tier 3 — independent routing oracle (OTP): thousands of pairs, not 20 — controlling GTFS+OSM snapshot, walk speed, boarding slack, transfer limits, access mode, station entrances. OTP post-processes itineraries (transfer optimization), so modest structural differences are expected; compare arrival time and journey structure.
  • Tier 4 — observed world: GTFS-RT historical travel times, agency trip planners, real toll calculators, parking prices, known fare examples, manually traveled benchmarks.
  • Tier 5 — performance acceptance: GTFS preprocess, street-access preprocess, GPU init, each round, transfer relax, compaction, D2H, Parquet write, complete batch wall time. Continue the custom GPU path only if it beats an optimized in-process CPU RAPTOR end-to-end — not merely repeated OTP API calls.
  1. Finish a strict CPU RAPTOR reference.
  2. Accurate walk-only H3→stop connectors.
  3. Prove stop-profile vs H3-origin-batch architecture on the subway pilot.
  4. Compile + validate the GPU kernel on 1K and 10K origins.
  5. Fix tiled output before the full 535K-origin run.
  6. Journey reconstruction.
  7. Transit fares only.
  8. P&R / K&R / ride-hail as distinct modes.
  9. Vehicle energy, tolls, parking.
  10. Then NJ Transit, PATH, LIRR, Metro-North, AirTrain.

10. Bottom line

Proceed with the GPU RAPTOR pilot, but not as the final architecture. Production shape = H3 spatial index + sparse street access/egress + RAPTOR transit core + itinerary reconstruction + independent monetary-cost engine + query-time composition. The GPU's value is large repeated fixed-date transit-profile/isochrone batches; it should not replace the street router, fare engine, mode-state logic, or itinerary layer. Highest-value change: factor around sparse H3→stop access + reusable stop-to-stop transit profiles rather than brute-forcing the timetable per H3 cell.

Comments