Home · ASPLOS 2024 · Graph Processing & Analytics

TGLite: A Lightweight Programming Framework for Continuous-Time Temporal Graph Neural Networks

Yufeng Wang and Charith Mendis

Conference
ASPLOS 2024
Published
2024
Pages
1183-1199
Citations
11

Graph neural networks that learn from timestamped event streams — fraud as it happens, recommendations that know what you clicked five minutes ago — are written today either against configuration files or by hand-rolling the bookkeeping, because the standard building block borrowed from static graph libraries has no notion of time and no idea what came before or after it in the chain; TGLite (Illinois) replaces it with a temporal block that carries timestamps, links to its neighbours in the multi-hop chain, lets you edit the target nodes before sampling, and can be handed post-processing hooks the runtime runs for you — which is what turns three known redundancy optimisations into operators you compose in one line each rather than bookkeeping you have to remember.

The argument in one glance

  1. Problem

    Real graphs change, and when they changed matters

    A graph neural network learns a vector for each node by repeatedly mixing in information from its neighbours. That works when the graph stands still, and real graphs do not — social networks gain users, payment networks gain transactions, knowledge graphs gain facts. Temporal versions of these models learn from both the structure and the timing, and they beat the static ones on anything time-sensitive. There are two ways to feed them time: a series of frozen snapshots, or a continuous stream of edges each stamped with the moment it appeared. The stream carries far more information and is what real-time fraud detection and time-aware recommendation actually need.

  2. What people did

    Reuse the tooling built for static graph neural networks

    The mature libraries for static models are built around a message-flow graph: an object holding one hop of the relationship between a set of target nodes and their neighbours, on which you call an operator that does the message-create, aggregate and update steps. It is a good abstraction, it is widely understood, and the leading framework for temporal models on continuous streams builds directly on it.

  3. Why it fell short

    That object has no time in it, no memory of the chain it belongs to, and nowhere to put an optimisation

    Timestamps are simply absent, so the user carries them separately and threads them through by hand. Each object is standalone, so a two-hop aggregation means orchestrating by hand which result flows into which. Both endpoints must be supplied up front, which blocks the optimisations that want to rewrite the target list before neighbours are ever sampled. And there is no place to attach the cleanup that a rewriting optimisation needs afterwards, so the user must remember to run it between layers. The result is that the leading framework for this workload is driven by configuration files rather than a programming interface, and adding a redundancy optimisation to it means writing a C++ extension.

  4. This paper's idea

    Design the block for time, for chains, and for being edited

    TGLite keeps the one-hop idea and changes four things about it. Timestamps are first-class. Blocks form a doubly linked list, so a block can find the rest of the chain and a multi-hop operator can walk it. Neighbour information is optional, so the target node-time pairs can be rewritten before any sampling happens. And a block accepts hooks — functions the runtime invokes at the right moment after computation, which is exactly where a rewriting optimisation needs to undo itself. With those four, deduplication, memoisation, time precomputation and prefetching all become single-line operators applied to a block, and the tedious part disappears into the framework.

Why this problem is worth solving

Most graph learning research assumes the graph is a fixed object, and almost no interesting graph is.

A graph represented not as a structure but as a stream of edges, each carrying the timestamp at which it appeared. The alternative representation slices time into intervals and stores a static snapshot of each, which is simpler to compute on but throws away everything about ordering within a slice. The continuous form keeps the full ordering, which is what lets a model answer questions that depend on what happened minutes rather than days ago — whether this transaction resembles the last few from this account, what a user clicked just now.
is the representation that matters for the applications people most want these models for, and it is the one the tooling supports worst.

What makes it awkward to program is that time turns every computation into a query about a moment.

The core computation of these models. For a target node and a target time, gather the neighbours whose edges predate that time, build a message from each one using its features, the edge features and how long ago the edge occurred, reduce those messages to a single vector, and combine it with the node itself. Stacking layers means doing this recursively, so a two-layer model asks about the neighbours of the neighbours — each with their own earlier cutoff time. The awkwardness is that the same node appears many times at many different times, and every one of those is a separate thing to compute.
is where all the runtime goes and also where all the redundancy hides: the same node-time pair often appears repeatedly within a batch, the same embedding was often computed a moment ago, and the time-encoder is often asked for the same vector over and over. Prior work identified all three. What was missing was somewhere to put the fix.

What people did before, and where it broke

  • Use the static graph libraries directly. They provide the message-flow object, message-passing operators, and a mature ecosystem. Where it broke. The object has no timestamps, so the user manages them separately; it is standalone, so multi-hop chaining is manual; it requires both endpoints up front, so the target list cannot be rewritten before sampling; and it has no hook mechanism, so any optimisation that rewrites the input must be undone by hand between layers.
  • Frameworks for the snapshot style of temporal graph. These exist and are reasonable, providing implementations of spatio-temporal models over discrete snapshots, some tuned around a particular use of recurrent networks. Where it broke. They target the wrong representation. Snapshot models are a different computation, and the frameworks built for them do not carry over to the stream style at all.
  • The leading framework for continuous-time models. It does support this workload, contributes a temporal compressed-sparse-row format and a parallel sampler, and scales training well enough to be the natural baseline. Where it broke. It is a training pipeline rather than a programming interface — users configure it through YAML files. You can of course clone the source and add components, but then you are working inside its structure, which the paper argues discourages novel compositions and orderings. Beyond the graph format and the sampler it offers little that exploits what makes continuous-time graphs distinctive.
  • Hand-write the model with the optimisations inline. This is what the prior redundancy work did, and it is fast. Where it broke. The paper reproduces such an implementation and walks through what it costs: manual pre- and post-processing around deduplication, manual cache hit and miss handling, a tricky recursive control flow, a one-off neighbour-finding data structure that every project reinvents, and a C++ extension with Python bindings to make any of it efficient. None of it is reusable and all of it must be right.
  • The gap. Every piece of this was solved somewhere — the redundancy optimisations were published, the sampler existed, the message-flow idea was mature — and none of it composed, because the central data structure had no place to attach time, no knowledge of the chain it was part of, and no way to schedule its own cleanup.

How TGLite restructures the computation

From a hand-orchestrated recursive model to composed block operators

  1. new

  2. new

  3. new

  4. new

  5. new

The user writes an iterative loop over layers with one operator call per optimisation, against a PyTorch backend that supplies the tensor math and autodiff.

The idea

Change what the central data structure knows about, and the operators follow. The temporal block differs from the static message-flow object in four ways, and each one exists to unblock something specific. Timestamps are part of the representation, so time-aware operators can be defined on it rather than around it. Blocks link into a doubly linked list, so a multi-hop aggregation is one operator that walks the chain rather than an orchestration the user performs. The neighbour side is optional, so the target list can be rewritten while it is still cheap to do so. And blocks accept hooks, which is the small piece that makes rewriting optimisations composable at all, because a rewriting optimisation is really two halves and the second half happens somewhere the user was not looking. Building an abstraction that separates what a graph operator computes from how it gets scheduled, so that both can be varied independently, is the same design instinct as the unified graph-operator abstraction covered elsewhere on this site.

The optimisations were already known; what was missing was somewhere to put them. Deduplication, memoisation of embeddings and precomputation of time vectors all come from earlier work on this exact model family, and TGLite does not claim them as new. What it claims is that they were previously unreachable — applying them meant manual pre-processing, manual cache hit and miss handling, and a C++ extension, all interleaved into the model by hand and none of it reusable. As block operators they are one line each, they compose in any order, and the paper is careful to state that they are semantics-preserving, with an accuracy table to back it up. The general observation that a graph workload repeats the same work many times over and that recognising the repetition is worth more than speeding it up is the same one behind the concurrent-traversal alignment work covered elsewhere on this site.

Time is exactly what the static abstractions dropped, and dropping it is what forces the manual work. Every difficulty in the paper traces to the same root. A static message-flow object cannot hold a timestamp, so the user holds it. It cannot express that this node at this time is a different thing from the same node at another time, so the deduplication that exploits exactly that has nowhere to live. It cannot know its position in a chain of hops with successively earlier cutoffs, so the chaining is manual. The remedy is not clever; it is to put time back into the representation and let everything else be derived. Treating the evolution of a graph over time as the thing the representation should be organised around, rather than a complication layered on top of a static structure, is the same reframing as the evolving-graph analytics work covered elsewhere on this site.

Walking one training batch through TGLite

Two layers of temporal attention over a batch of timestamped edges

The model asks for embeddings of the endpoints of each edge in the batch, at the moment that edge occurred.

Step 1 of 5

Wrap the batch without materialising anything

A batch is a thin reference to a set of edges in the temporal graph rather than a bundle of arrays. Nothing is copied until an operator actually needs it. From the batch, a head block is created holding the target node-time pairs.

The graph itself is stored as timestamp-sorted coordinate lists, because the common case is walking edges in chronological order, and converted to compressed sparse row form when a model needs neighbourhood lookups. The framework builds and manages both formats without the user asking.

Why it works

  • Rewriting the targets before sampling shrinks everything downstream. Deduplication and cache filtering operate while the block holds only target node-time pairs → the neighbour sampling that follows runs on a smaller set, and so does every computation built on it → the saving compounds through the layer instead of being applied at the end.
  • A hook is where the second half of an optimisation belongs. Any optimisation that rewrites its input has to restore the expected output afterwards, at a moment between layers → the block carries that function and the runtime invokes it → the user cannot forget it, and the two halves stay together in one operator.
  • Linked blocks turn orchestration into traversal. Each block knows its predecessor and successor → a multi-hop aggregation is one operator that walks the chain and applies a user function → what was hand-written coordination between separate objects becomes a library call.
  • Optional neighbours make the target list editable. The block is valid with only one side populated → operators can manipulate targets as a first-class activity rather than as preparation done outside the abstraction → which is the specific structural property the static message-flow object lacks.
  • Owning the data movement lets the framework pin memory. The block chain knows what data the whole computation will need → a prefetch operator can pull it through a pool of pre-allocated pinned host memory in one go → in the case where features live on the host this is worth 1.29 to 1.62 times on its own, before any algorithmic optimisation.

What it costs (trade-offs and scope)

  • Without the optimisation operators, the framework is often slower than the baseline. In the case where all data already sits on the GPU, TGLite alone runs at 0.83 to 0.95 times the baseline for one model on the A100, and inference ranges from 0.85 to 1.61 times — the low end being a real regression. The paper says plainly that applying the optimisation operators helps offset these slowdowns. The speed comes from the optimisations, not from the abstraction.
  • The abstractions themselves are worth almost nothing in performance. The ablation replacing temporal blocks with static message-flow objects costs about 9% and 3% in the two training cases. Removing the hook mechanism costs no measurable performance at all. What both save is user code — roughly 200 lines and 49 lines respectively. That is a legitimate contribution, and it is a programmability contribution rather than a speed one.
  • Two operations are measurably worse than the baseline, by design. The breakdown shows attention at 14.39 seconds against 9.20, because the segmented softmax calls an external kernel library and pays for data transformation and boundary crossing, and time-encoding at 2.88 against 1.76, because computing deltas is a separate step where the baseline gets them free while sampling. The authors describe this as choosing generality over efficiency and name the attention issue as future work.
  • The optimisations are ported, not invented. Deduplication, memoisation and time precomputation all come from prior work on this model family. The contribution is that they compose; the techniques are someone else.
  • Not every optimisation is worth applying. For one of the four models, enabling the full optimisation set produces lower speedups than the prefetch alone, which the paper attributes to the precomputed-time operators not paying for themselves there. For the simplest model, no optimisations beyond prefetch apply at all.
  • The headline large-scale numbers are single cells. The 9.02 times training and 15.63 times inference figures are one model on one dataset on one machine. The general statement for the large datasets is at least 1.15 times, and on the A100 the WikiTalk speedups are 1.15 to 1.40 times where the same runs on the older V100 give 2.26 to 5.23 — the advantage shrinks as the hardware improves.
  • No multi-GPU and no snapshot-style graphs. The baseline framework supports multi-GPU training and TGLite does not, which the authors state and defer to future work along with support for the discrete-time representation.
  • One accuracy gap is a baseline artifact rather than a result. The wider inference accuracy margin on one dataset is attributed to the baseline training script not saving node memories after training and instead recreating them, which is a property of the comparison rather than of either design.

The evaluation

Three ways to build a continuous-time temporal graph model

PropertyHand-written with inline optimisationsTGL (the baseline framework)TGLite with optimisation operators
How a researcher expresses a model
Training speed when data is already on the GPUReference
Training speed when data lives on the hostNot evaluated
Large datasetsNot evaluated
What the abstractions themselves buyNothing to compareStatic message-flow objects with timestamps carried alongside
Four models over four standard datasets plus two large ones, on a V100 and an A100, with accuracy tables confirming the optimisations are semantics-preserving.Underlined values explain themselves when clicked.

Check yourself

Check your understanding

  1. 1. Why does making the neighbour side of a block optional matter?

  2. 2. What does the hook mechanism contribute, according to the paper own ablation?

  3. 3. Where do TGLite speedups actually come from?

  4. 4. How should the reported 9.02 times training speedup on large datasets be read?

Sources

The paper is “TGLite: A Lightweight Programming Framework for Continuous-Time Temporal Graph Neural Networks” by Yufeng Wang and Charith Mendis, published at ASPLOS 2024 Volume 2, pages 1183-1199, DOI 10.1145/3620665.3640414. Both authors are at the University of Illinois at Urbana-Champaign. It is open access under a Creative Commons licence and carries an artifact appendix.

This explainer is written from the full text, read through a proxy of the ACM Digital Library copy. Everything above comes from the paper’s own sections 2 through 5 and its appendices: the message-passing formulation with its time-restricted neighbourhood, and the attention and memory-based model families; the review of deduplication, memoisation and time precomputation as prior redundancy work; the side-by-side listings of a hand-written model against a TGLite one, with the observations about manual optimisation bookkeeping, the required C++ extension, the recursive control flow and the one-off neighbour-finding structure; the four design differences between the temporal block and the static message-flow object, namely the doubly linked list, optional neighbour information, the hooks mechanism and cached block data; the operator table covering sampling, segmented softmax and reduction, coalescing, pull-style aggregation, push-style propagation, prefetching, deduplication and caching; the data objects for the graph, batch, sampler, memory and mailbox, with the coordinate-to-compressed-sparse-row handling; the dataset table with its six benchmarks; the training results of 1.06 to 1.81 times all-on-GPU and 1.41 to 3.43 times host-to-GPU, including the 0.83 to 0.95 times regression without optimisations; the accuracy table confirming the optimisations preserve semantics; the breakdown showing attention at 14.39 against 9.20 seconds and time-encoding at 2.88 against 1.76; the inference results with their 0.85 to 1.61 times range; the ablations on individual optimisations, on blocks versus message-flow objects at 9% and 3%, and on the hook mechanism; and the large-scale table with its at-least 1.15 times claim, its 9.02 and 15.63 times single cells, and the out-of-memory failures of the baseline.

Related papers on Graph Processing & Analytics

See all 26 →