Repository navigation
Conversation
|
Thanks for the PR. Since in this approach the graph is a compile-time constant, it may give silently wrong results when the compiled function is applied to a different graph. Reproducer: a compiled model silently reuses the graph it was compiled withusing GNNLux, Lux, Reactant, Random
model = GCNConv(3 => 2)
ps, st = Lux.setup(Xoshiro(0), model)
x = randn(Xoshiro(1), Float32, 3, 6)
g1 = GNNGraph(([1, 2, 3, 4, 5, 6], [2, 3, 1, 5, 6, 4]); num_nodes = 6)
g2 = GNNGraph(([1, 1, 1, 2, 2, 3], [4, 5, 6, 5, 6, 6]); num_nodes = 6)
f(g, x, ps, st) = first(model(g, x, ps, st))
ps_d, st_d, x_d = (ps, st, x) |> reactant_device()
compiled = @compile f(g1, x_d, ps_d, st_d) # graph stays on the host
y = Array(compiled(g2, x_d, ps_d, st_d))
y ≈ f(g2, x, ps, st) # false
y ≈ f(g1, x, ps, st) # true: g1 was baked into the compiled programThe host graph's edge indices become constants in the compiled program, so calling it with another graph of the same type gives no error and returns the result for the original graph. This affects mini-batched training and any compiled model reused across graphs. #730 is more general in that respect, i.e. it goes further by ensuring that the graph can be traced, which is needed for the tutorials to fully work. With #730: device graphs are inputs, so a compiled model works across graphsWe also move the graphs to the device; this would fail on this branch:using GNNLux, Lux, Reactant, Random
model = GCNConv(3 => 2)
ps, st = Lux.setup(Xoshiro(0), model)
x = randn(Xoshiro(1), Float32, 3, 6)
g1 = GNNGraph(([1, 2, 3, 4, 5, 6], [2, 3, 1, 5, 6, 4]); num_nodes = 6)
g2 = GNNGraph(([1, 1, 1, 2, 2, 3], [4, 5, 6, 5, 6, 6]); num_nodes = 6)
f(g, x, ps, st) = first(model(g, x, ps, st))
ps_d, st_d, x_d, g1_d, g2_d = (ps, st, x, g1, g2) |> reactant_device()
compiled = @compile f(g1_d, x_d, ps_d, st_d) # graph moved to the device
Array(compiled(g1_d, x_d, ps_d, st_d)) ≈ f(g1, x, ps, st) # true
Array(compiled(g2_d, x_d, ps_d, st_d)) ≈ f(g2, x, ps, st) # trueThe graph's edge indices are inputs to the compiled program, so the same compiled model gives correct results for any graph with the same numbers of nodes and edges. Graphs of a different size need a new That said, even #730 does not correctly deal with different batch sizes yet. Note that the Reactant tests here are in a separate environment that is not part of CI. |
|
Thanks for catching this. You’re right: my current test compiles with a host graph, so its edge indices can be baked into the compiled program. Reusing it with another graph could silently produce the result for the original topology. I’ll investigate the device-graph approach and compare with #730 so the graph is traced as a runtime input, then add a test that reuses the compiled function across same-shaped graphs. I’ll also account for the need to recompile when graph shapes change. |
|
Thanks, nice work getting this through Reactant! I checked the branch out and ran it locally (Julia 1.13.3, Reactant 0.2.307). Verified
Requested change
Suggestions 🧔♂️🤖 Assisted by AI |
|
Fixes #710 SummaryFix Reactant compilation for Root causeWhen a host-side graph is used during compilation, its edge indices can be embedded as constants. Reusing the compiled function with another graph of the same shape may then silently use the original topology. Changes
Tests
|
Fix #710
Summary
Fix Reactant compilation failures in
GCNConvgraph normalization and message passing.Reactant traced scalar types are now mapped to their underlying numeric types before graph-side degree and adjacency allocations, ensuring compatibility with Reactant compilation.
Reactant support is provided through an optional GNNGraphs extension, keeping Reactant optional for normal CPU users.
Changes
Tests
All executed test suites passed:
The compiled
GCNConvoutput has the expected shape(2, 6)and matches the CPU result within the test tolerance.