Home · ASPLOS 2023 · Machine Learning Systems

STI: Turbocharge NLP Inference at the Edge via Elastic Pipelining

Liwei Guo, Wonkyo Choe and Felix Xiaozhu Lin

Conference
ASPLOS 2023
Published
2023
Pages
791-803
Citations
22

Running a language model on your phone keeps your data private and avoids network round-trips, but the model is too big to fit in memory (holding it there gets your app killed) and too slow to load on demand (a couple of seconds when you have a few hundred milliseconds), and naively overlapping loading with compute doesn't help because transformer layers are IO-bound — a DistilBERT layer takes 339ms to load but only 95ms to compute, so the processor stalls 72% of the time waiting; STI (University of Virginia) fixes this by cutting each layer into independently-tunable SHARDS, profiling how much each matters to accuracy, then spending its tight IO budget where it counts — loading the important shards at high precision and the rest at 2-6 bits or skipping them, pipelined against compute with a small preload buffer to avoid the startup stall — hitting nearly full accuracy at 1-to-2 orders of magnitude less memory (down to 1-5 MB, a 204x cut).

The argument in one glance

  1. Problem

    A language model on your phone must be both fast and small — and it is neither

    Running NLP inference on-device (rather than in the cloud) keeps a user's text private and skips the network round-trip, and users expect each turn to respond in a few hundred milliseconds. But modern transformer models are big — a compact DistilBERT is 170 MB — and that size collides with the two scarcest resources on a phone. Memory: keeping the model resident multiplies an app's footprint, making it a prime target for the mobile OS's low-memory killer, which recycles it after just a few inferences. Latency: loading the model from storage on demand takes about 2.1 seconds, several times the user's patience. So the model is simultaneously too big to keep in memory and too slow to load when needed.

  2. What people did

    Pipeline it: load each layer while computing the previous one

    The classic fix for load-then-compute is pipelining. Instead of loading the whole model and then running it, overlap the two: while the processor computes layer k, stream layer k+1's parameters from storage in the background, so IO and compute happen at once and the loading time is hidden behind the computation. This works beautifully for compute-heavy models where each layer takes longer to run than to load, and it is the natural way to launch execution quickly without holding the whole model in memory.

  3. Why it fell short

    Transformer layers are IO-bound, so pipelining hides almost nothing

    Pipelining only hides IO when computation is the longer of the two — and for transformers the opposite is true. The core operations (especially attention) have LOW ARITHMETIC INTENSITY: they move a lot of parameters but do relatively little math per parameter, so loading dominates. In DistilBERT a single layer takes 339 ms to load its parameters but only 95 ms to compute — so even with perfect overlap, the compute finishes and then stalls, waiting on IO, more than 72% of the time. Pipelining a workload whose bottleneck is IO just leaves the processor idle behind the slowest stage. The real problem is not the order of loading and compute but that there are simply too many parameter bytes to move within the latency budget, and no scheduling of a fixed set of bytes can fix that.

  4. This paper's idea

    Spend a tight IO budget on the parameters that matter most

    STI (University of Virginia — Guo, Choe, Lin) reframes the problem as spending a scarce IO budget wisely. Since not all parameters matter equally, do not load them all at full precision. First, MODEL SHARDING: cut each layer into independently-tunable shards (each an attention head plus a slice of the feed-forward network), store each shard at several bitwidths (2-6 bits up to 32), and profile how much each shard contributes to accuracy. Second, ELASTIC PIPELINE PLANNING: given a target latency, pick the largest submodel that fits, then spend the available IO budget by loading the most important shards at high precision and the rest at low precision (or skipping them), overlapped with compute and bootstrapped by a small preload buffer that fills the pipeline's early stages so it never stalls at the start. The result is near-full accuracy (within 0.1 percentage points) at 1-to-2 orders of magnitude less memory — down to 1-5 MB, a 204x reduction.

Why this problem is worth solving

Keeping language understanding on the device instead of the cloud is a genuine privacy and latency win, and the only thing blocking it is the awkward fit between a large model and a small phone. The squeeze is fundamental because a transformer’s cost is dominated by moving its weights, not by the math it does with them, which makes the workload

a computation whose speed is limited by how fast data can be MOVED, not by how fast the processor can compute. Arithmetic intensity is the ratio of math operations performed to bytes of data loaded; when it is low, the processor finishes its work quickly and then sits idle waiting for the next chunk of data to arrive. Transformer layers, and attention in particular, have low arithmetic intensity: they load a great many parameters but perform relatively little computation per parameter. So on a phone, where parameters live in slow storage, inference time is governed by parameter LOADING — a DistilBERT layer spends 339 ms loading and only 95 ms computing — which means the whole exercise is really a data-movement problem wearing the disguise of a compute problem
. Recognizing that reframes what any solution must do: not schedule compute better, but move fewer parameter bytes, or move only the bytes that matter.

The two naive options fail precisely because they treat the model as an all-or-nothing block of bytes. Holding it in memory pays the full memory cost forever; loading it on demand pays the full IO cost every time; and even pipelining, which cleverly overlaps the two, cannot help when IO is the longer stage, because it still moves every byte. The escape is to stop treating the model as indivisible and instead make it

a model decomposed so that its parameters can be selected and compressed piece by piece, rather than loaded whole. STI cuts each transformer layer into vertical SHARDS — each shard is one attention head plus a fraction of the feed-forward network’s neurons, a slice that is self-contained enough to run on its own and preserves the multi-headed structure. Each shard is stored at several fidelity versions, from full 32-bit down to 2-6 bits, so the same shard can be loaded cheaply and roughly or expensively and precisely. Because any subset of shards at any mix of bitwidths still forms a valid (if less accurate) model, the model becomes elastic: you can assemble a version of it to fit whatever memory and latency budget you have
. Once the model is elastic in this way, the question changes from “how do I move all these bytes fast enough” (impossible) to “given the bytes I can afford to move, which ones buy the most accuracy” — and that is a question with a good answer, because the shards are not equally important and their importance can be measured.

What people did before, and where it broke

  • Hold the whole model in memory (PreloadModel). Load once, execute instantly thereafter. Lowest latency, but it inflates the app’s memory footprint several-fold, so the mobile low-memory killer evicts it after a few inferences — and STI matches its accuracy using 204x less memory.
  • Load on demand, then execute (Load&Exec). Keep memory tiny by loading the model from storage each time it is needed. But that load is about 2.1 seconds, many times a user’s few-hundred-ms budget, so it simply misses the latency target.
  • Layerwise IO/compute pipelining (StdPL). Overlap loading layer k+1 with computing layer k. Sound for compute-bound models, but transformer layers are IO-bound (339 ms load vs 95 ms compute), so compute stalls over 72% of the time and the IO is barely hidden; STI beats it by 5.9-54.1 percentage points of accuracy.
  • Uniform quantization (StdPL at a fixed low bitwidth). Shrink every parameter to, say, 2 or 6 bits to move fewer bytes. This moves less but spends the savings blindly — it degrades important and unimportant parameters equally — so at a given budget it reaches lower accuracy than allocating bits by importance (STI adds 3-21 pp over fixed-bitwidth pipelining).
  • The gap. No prior approach both made the model elastic at a fine grain AND spent a latency-bounded IO budget on the shards that most improve accuracy, with a preload buffer to kill the startup stall. STI does exactly this, turning an impossible move-everything problem into a solvable move-what-matters one.

How STI works

Shard the model, rank the shards, then plan an IO budget that favors the important ones

  1. Diagnose the true bottleneck: IO, not compute new

    STI starts from the measurement that transformer inference on a phone is IO-bound — parameters take far longer to load than to compute (339 ms vs 95 ms per DistilBERT layer, over 72% stall). Every design decision is therefore organized around the IO budget, treating parameter bytes as the scarce resource to allocate rather than FLOPs.

  2. Shard each layer into independently-tunable pieces new

    Each layer is cut into M vertical shards, where a shard is one attention head plus 1/M of the feed-forward neurons — a self-contained slice that preserves multi-headed attention. Each shard is pre-compressed into several fidelity versions at different bitwidths (from 2-6 bits up to full 32), so a shard can be loaded cheap-and-rough or expensive-and-precise.

  3. Profile each shard's importance to accuracy new

    One time per fine-tuned model, STI ranks shards: it raises each shard in turn to full 32-bit precision, runs the model on a dev set, and records the accuracy it recovers. This yields an importance table saying which shards most deserve precious bits — a small offline cost reused across all future inferences.

  4. Compute-plan the biggest submodel that fits the latency new

    Given a target latency T, STI enumerates submodel sizes (n layers by m shards) and picks the LARGEST whose computation fits within T, maximizing the model capacity (FLOPs) it can afford to run. This sets how much model there is to fill with parameters.

  5. IO-plan: spend the budget on the important shards new

    STI computes an Accumulated IO Budget per layer — because computing layer k-1 takes time, that time is "bonus IO" during which layer k can load, so budgets accumulate down the pipeline. It first assigns every shard a uniform low bitwidth, then iteratively UPGRADES the most important shards toward 32-bit, guided by the importance table, until the accumulated IO budget is exhausted.

  6. Preload buffer to bootstrap the pipeline new

    A small buffer (1-5 MB) preloads shards from the earliest layers, so the moment the user engages, execution starts with the pipeline's first stages already in memory and never stalls at the beginning. The IO time saved by the buffer folds into the very first layer's budget, giving the whole pipeline a running start.

Built and evaluated on two commodity SoCs — an Odroid (ARM CPU) and a Jetson (GPU) — across GLUE NLP classification tasks with BERT-family models, under practical target latencies (around 200-400 ms), against preloading, load-and-execute, and standard layerwise pipelining (at full and fixed low bitwidths).

The idea

When you cannot move all the bytes, move the ones that matter. The reframing that makes STI work is recognizing that on a phone, transformer inference is a data-movement problem, not a compute problem — the processor is idle most of the time waiting for parameters — so the binding constraint is a latency-bounded IO budget. No scheduling of a fixed byte count fixes an IO bottleneck; you have to move FEWER bytes, and the only way to do that without wrecking accuracy is to move fewer bytes of the parameters that matter LESS. STI makes the model elastic (shards at tunable bitwidths), measures which shards matter (importance profiling), and then allocates the IO budget to the important ones. This is the spend-the-scarce-resource-where-it-counts discipline the site rewards again and again — the real-time-rendering page pruning by a value-over-cost efficiency and rendering the periphery cheaply, the 8-bit-numerics page putting precision where the data clusters, the FHE-compiler page spending effort only where it pays — here spending IO bandwidth on the shards that most improve accuracy.

Find the true bottleneck, and let it reorganize the whole design. Everyone knew pipelining hides IO; STI’s contribution begins with measuring that for transformers it does NOT, because the layers are IO-bound (low arithmetic intensity), and then building everything around that fact. Once IO is the scarce resource, the design questions all change: the planner’s currency becomes parameter bytes, not FLOPs; the compute time of early layers becomes a budget for loading later ones (the accumulated-IO insight); and the startup stall becomes worth a dedicated preload buffer. This is the measure-the-true-cost discipline the site sees when a system discovers its real limit is cache, or communication, or contention rather than compute — the arithmetic-intensity framing here is the same roofline reasoning — and it is what lets STI stop optimizing the thing that was never the bottleneck.

Make the model elastic so it can degrade gracefully to any budget. Because any subset of shards at any mix of bitwidths is still a valid model, STI can assemble a version to fit whatever latency and memory it is handed, trading accuracy smoothly for resources rather than failing when the budget is tight. Larger budget, more high-precision shards and more accuracy; smaller budget, fewer or rougher shards and graceful accuracy loss — never the all-or-nothing of preload (too much memory) or on-demand load (too much latency). This is the resource-elastic, graceful- degradation posture the site’s accuracy-scaling and anytime- inference work shares: build a knob that turns quality against cost continuously, and a planner that sets it to the best point the budget allows.

Why loading dominates, and how STI spends the budget

From an impossible move-everything problem to a solvable move-what-matters one

On-device transformer inference under a few-hundred-ms budget. Numbers from STI's evaluation.

Step 1 of 5

The mobile squeeze

The model is too big to keep resident (holding a 170 MB model bloats the app until the low-memory killer evicts it) and too slow to load on demand (about 2.1 seconds against a few-hundred-ms budget). Neither all-in-memory nor load-on-demand can satisfy both the memory limit and the latency target at once.

on-device is worth it for privacy and to avoid network round-trips, but only if it responds in a few hundred ms
the two naive options sit at opposite, both-unacceptable ends of the latency-memory trade-off

Why it works

  • Transformer inference on a phone is IO-bound, not compute-bound (339 ms load vs 95 ms compute) → STI treats parameter bytes, not FLOPs, as the scarce resource and organizes planning around an IO budget → it optimizes the stage that actually limits latency.
  • Not all parameters matter equally to accuracy → STI shards the model and profiles each shard’s importance → it knows which bytes to spend its IO budget on.
  • Any subset of shards at any bitwidths is a valid model → STI can load important shards at full precision and unimportant ones at 2-6 bits or not at all → it moves far fewer bytes for nearly the same accuracy.
  • Computing an earlier layer takes time during which a later layer can load → the Accumulated IO Budget grows down the pipeline, and bits are upgraded by importance until it is spent → the overlap is used to raise precision exactly where it helps most.
  • Pipelines stall while their first stage loads → a small preload buffer holds the earliest shards so execution starts immediately → the startup stall that would blow the latency budget disappears.

What it costs

  • Transformer classification, not generation. STI targets encoder transformers (BERT and variants) on classification tasks; the authors argue the ideas extend to generative models like GPT-2 but do not evaluate them, and mobile generative use was nascent at the time.
  • Bounds execution time, not wall-clock time. STI keeps the model’s execution within the target latency, but guaranteeing the total wall-clock response (including OS scheduling delays) would need real-time OS support it does not provide.
  • Per-model, per-device profiling. The shard-importance table must be generated for each fine-tuned model and device class — a one-time preprocessing cost, done in the cloud or on-device, but not free and not transferable across models.
  • Diminishing returns at loose budgets. The benefit is largest at tight latencies (around 200-400 ms); as the budget grows, model accuracy saturates and STI’s advantage over simpler schemes shrinks.
  • Higher energy for higher accuracy. By keeping IO and compute busy to maximize accuracy under the budget, STI uses notably more energy than low-accuracy baselines — it optimizes accuracy per latency, not energy.

The evaluation

Two commodity SoCs — an Odroid with an ARM CPU and a Jetson with a GPU — running BERT-family models on GLUE NLP classification tasks, under practical target latencies (roughly 200-400 ms), compared against preloading the full model, load-and-execute, and standard layerwise pipelining at full and fixed low bitwidths.

Running a big NLP model on a phone under a latency budget

PropertyHold model in memoryLoad on demand / layerwise pipelineSTI (elastic sharded pipeline)
Memory footprint
Meets the latency target?
Handles the IO bottleneck?
Accuracy under a tight budget
What it optimizes
STI's advantage is largest exactly where the naive options both fail — a tight latency budget with little spare memory — because it is the only one that moves fewer parameter bytes AND spends those bytes on the shards that most improve accuracy. Its edge narrows as the budget loosens and model accuracy saturates.Underlined values explain themselves when clicked.

Check yourself

Check your understanding

  1. 1. Layerwise pipelining — loading layer k+1 while computing layer k — normally hides loading time, yet the paper shows it barely helps for on-device transformer inference. Predict why.

  2. 2. STI shards the model, profiles each shard's importance to accuracy, and then allocates more bits to important shards. Predict why importance-weighted bit allocation beats simply quantizing every parameter to the same low bitwidth.

  3. 3. STI uses a small preload buffer (1-5 MB) that holds shards from the earliest layers. Predict why this small buffer matters despite being a tiny fraction of the model.

  4. 4. STI makes the model "elastic" — any subset of shards at any mix of bitwidths is a valid model — and plans a submodel to fit the given latency and memory. Predict why this elasticity is essential rather than just convenient.

Sources

Related papers on Machine Learning Systems

See all 212 →