alphaFeedback

WorksComputer Science

EpiLoRA: Fusing Many LoRA Adapters into Low-Precision GEMMs for Serving and RL

AI contributions
Not recorded for this paper
View details

Abstract

Low-rank adapters (LoRA) let one base model serve many fine-tuned variants: multi-tenant engines keep a single copy of the base and batch the requests of many tenants, each with its own adapter. As base models move to 8-bit (FP8) and 4-bit formats, their general matrix multiplications (GEMMs) become cheaper, but the adapters do not: engines run them as separate small GPU kernels beside the GEMM, whose latency takes a growing share of every decoding step. An adapter projects its input down to a low rank and back up, and our key observation is that the GEMM needs the down-projection only in its epilogue, the final stage in which it writes its output. EpiLoRA, a plugin for the vLLM engine, therefore launches the down-projection first and the low-precision GEMM as a dependent kernel that streams its weights meanwhile and adds every tenant's up-projection in its epilogue. On NVIDIA Hopper GPUs, EpiLoRA decodes for a single adapter with up to 24% lower time per token than vLLM's fastest configuration, and faster than SGLang and TensorRT-LLM; with many tenants, its adapter kernels deliver more throughput than vLLM's on most GPU-model pairs we tested. The same low-precision base also trains: a LoRA reinforcement-learning trainer that computes on the engine's FP8 weights needs no 16-bit copy, so a 27B model trains on one 48 GB GPU, and an engine that then keeps activations in 16 bits removes most of the remaining mismatch between the model generating training data and the one trained.

Introduction

Low-rank adapters (LoRA) (Hu et al., 2022) have become the standard way to specialize a large language model without copying it. An adapter adds a low-rank update BABA to each frozen weight matrix WW of the model, where AA projects the layer’s input down to a small rank rr (typically 16–128) and BB projects it back up; together they hold a fraction of a percent of the model’s parameters. One base model can therefore carry thousands of fine-tunes. Multi-tenant serving engines exploit this by keeping a single copy of the base in GPU memory and batching the requests of many tenants, the users or applications that each bring their own adapter [5, 52]. Adapters are also what LoRA reinforcement learning (RL) trains: LoRA matches full fine-tuning at a fraction of the memory [49], and several services now train adapters for many users on shared bases [53, 38] (Yu et al. 2026a).

At the same time, base models are moving to low precision. NVIDIA’s Ada (e.g., L40S) and Hopper (H100 and H200) GPUs compute in 8-bit floating point (FP8) at twice their

rate in the 16-bit bf16 format (Micekievicius et al., 2022), and Blackwell GPUs compute 4-bit formats such as NVFP4 at four times that rate [48, 40]. Serving systems therefore quantize the base model’s general matrix multiplications (GEMMs) to these formats [27, 7] (Lin et al. 2025), and RL frameworks increasingly generate their training samples, called rollouts, with FP8 inference engines [30, 3] (Xi et al. 2026). Lower-precision bases make inference cheaper, but the cost of serving LoRA adapters remains largely unchanged in current engines.

The reason is that the adapter path stays outside the low-precision GEMM. Merging an adapter into a quantized base rounds the update onto the base’s coarse grid and can erase it [35] (Li et al. 2026), so engines keep adapters separate and compute them in their own GPU kernels beside the base GEMM [21, 5]. During decoding, when the engine generates one token per request at each step, this adapter path is a chain of small dependent kernels whose latency does not shrink with the base. The cheaper the base, the larger the adapter’s share of each step Figure 2.

Bar charts showing batch-1 latency and throughput changes for different adapter ranks and base precisions

Figure 2. Adapters cost relatively more on cheaper bases. Latency added at batch size 1 and throughput lost with vLLM’s LoRA path and default kernels, relative to the engine without LoRA: Qwen3-8B with bf16, FP8 and NVFP4 bases on a partition of a Blackwell GPU, one adapter of rank 16–128 or 32 of rank 64.

Existing systems address other parts of the problem. Punica and S-LoRA made batching many adapters practical [5, 52], and later systems decide where adapters are stored and which tenants share a batch ([4] (Wu et al. 2024; Li et al. 2024a)). SVDQuant fuses one low-rank branch, shared by every token, into a 4-bit GEMM ([23]), and LoRAFusion fuses several adapters into a bf16 GEMM for finetuning ([75]). However, none computes many tenants’ adapters in a low-precision GEMM for serving.

We present EpiLoRA, a plugin for the vLLM engine that computes every tenant’s adapter together with the low-precision base GEMM. A GEMM kernel streams the weights through its main loop and applies scales and writes its output in its epilogue. Our key observation is that the GEMM needs an adapter’s down-projection only in that epilogue, after the main loop has finished (Figure 1). During decoding, EpiLoRA therefore launches the down-projection first and its own FP8 or NVFP4 GEMM as a dependent kernel: the GEMM streams its weights while the down-projection runs, and then adds each tenant’s up-projection in its epilogue. The down-projection’s latency is then mostly hidden: on full-size layers with rank-64 adapters, at most about 2 μ\mus of it stays exposed. On Ada GPUs, whose standard FP8 GEMM is slow during decoding, EpiLoRA instead fuses all resident adapters into two kernels of its own. The kernels quantize activations exactly as vLLM does, so the base GEMM receives the same FP8 inputs as in vLLM.

Three execution paths comparing one stream, two streams, and EpiLoRA’s decode path

Figure 1. The GEMM needs the adapter’s down-projection only in its epilogue. One projection during decoding; a CUDA stream runs its kernels (GPU functions) in order, and two streams can run at once. (a) vLLM runs the down-projection (shrink, h=xATh = xA^{\mathsf{T}}) and the up-projection (expand, y+=hBTy += hB^{\mathsf{T}}) after the FP8 GEMM; (b) its dual-stream mode and TensorRT-LLM run them on a second stream and add the result after the GEMM; (c) EpiLoRA launches the shrink first and the GEMM as a dependent kernel, which starts while the shrink runs and adds hBThB^{\mathsf{T}} in its epilogue.

The low-precision base also benefits RL training, where frameworks that place the inference engine and the trainer on the same GPUs keep a separate bf16 copy of the base for the trainer ([15, 51, 33]). Because the FP8 engine then samples from a slightly different model than the trainer optimizes, FP8 rollout pipelines reweight each sample with an importance weight ([30]). EpiLoRA’s trainer instead computes the engine’s own FP8 weights, which saves enough memory to train a 27B model on one 48 GB L40S, where its bf16 weights alone do not fit, and lets one copy serve both the engine and the trainer. A matched engine also keeps its activations in bf16, as the trainer does, which removes most of the mismatch that remains once the weights agree.

We evaluate EpiLoRA on models from 8B to 72B parameters on Ada, Hopper and Blackwell GPUs, against the strongest configuration of each baseline we could build. Serving a single request (batch size 1), EpiLoRA takes 0.76–0.88× the time per output token of vLLM’s fastest configuration for 8B–9B models on Hopper GPUs, is faster than SGLang and TensorRT-LLM, and keeps its lead on a 72B model, on one GPU and across two. On the L40S it matches vLLM’s fastest configuration. With 32 tenants, whose batches are too large for the decode path, it delivers 1.05–1.09× vLLM’s throughput on 7 of 9 GPU–model pairs, from its adapter kernels on Hopper GPUs and fused adapters on the L40S.

In summary, our contributions are:

  • The decode path, which removes most of an adapter’s latency by adding the up-projection in the GEMM’s epilogue and overlapping the down-projection with its main loop, and a simple condition that bounds when the overlap hides the down-projection (§ 3.1 and § 5).

  • Multi-adapter kernels for FP8 and NVFP4 bases in a drop-in vLLM plugin that works with CUDA graphs and tensor parallelism (§ 3.2, § 5 and § 5.1).

  • A LoRA RL system on the same low-precision base, with a decomposition of the engine–trainer mismatch showing what each design choice removes (§ 3.3 and § 5.2).

Background and motivation

Why engines do not merge adapters. Merging an adapter into the base (W→W+BAW \rightarrow W + BA) produces one model per adapter, so the engine can no longer batch tenants. On a low-precision base, merging is also lossy: rounding W+BAW + BA to its grid changes only entries whose update exceeds half a quantization step, erasing small fine-tunes.

The cost of the separate adapter path. Each decoding step runs every linear layer (projection) of the model over the tokens of the current batch. Consider a projection over MM tokens, each belonging to a request that uses one of LL resident adapters. On an FP8 base, vLLM 0.28 runs four kernels for it ([5]): it quantizes the activations xx to FP8, runs the FP8 GEMM, computes the down-projection h=xA⊤h = xA^{\top} for each adapter’s tokens (the shrink), and adds the up-projection hB⊤hB^{\top} to the output (the expand). The activations are read twice and the output is written, read and written again, and the adapter weights are read by kernels that cannot share the GEMM’s work. None of these costs shrinks with the base. Tuning the LoRA kernels’ launch configurations, which we do for every vLLM baseline, reduces them, but with tuned kernels or vLLM’s dual-stream mode the adapter still adds 42–55% to a layer’s time at batch size 1 on Hopper GPUs (Table 1). On a Blackwell partition, one rank-64 adapter adds 16% to Qwen3-8B’s latency at batch size 1 with a bf16 base, 26% with an FP8 base and 43% with an NVFP4 base with vLLM’s default kernels (Figure 2).

μ\mus ↓\downarrowL40SH100H200Blackwell
Base GEMM, vLLM413126104286
Base GEMM, EpiLoRA416121102276
Adapter, vLLM97535754
Adapter, EpiLoRA46121017
EpiLoRA’s pathfusedoverlapoverlapoverlap
Layer, EpiLoRA / vLLM ↓\downarrow0.91×0.75×0.70×0.86×

Table 1. Where one adapter’s batch-1 cost goes. One Qwen3-8B decoder layer (four projections) at one decode token, in microseconds; adapter rows give each system’s cost over its own base GEMM, against vLLM’s fastest path on each GPU (Marlin on the L40S, CUTLASS elsewhere). L2-flushed kernel times only, so they do not add up to Table 2’s time per token.

Programmatic dependent launch. A GPU kernel runs as many thread blocks, which the GPU schedules onto its streaming multiprocessors (SMs). On Hopper and Blackwell GPUs, programmatic dependent launch (PDL) lets a kernel’s successor start before it finishes: the dependent’s thread blocks start as soon as the producer signals that they may, and wait for its results only at an explicit wait instruction. Inference engines use PDL to hide the launch latency between consecutive kernels, placing the wait at the start of the dependent kernel, so that only its setup overlaps the producer. EpiLoRA moves the wait to the end of the GEMM, just before its epilogue, so that the GEMM’s entire main loop overlaps the adapter’s down-projection.

LoRA RL on a low-precision engine. RL post-training alternates between generating rollouts with an inference engine and updating the model with a trainer, and it spends much of its time generating ([51] (Fu et al. 2025)). An FP8 inference engine and a bf16 trainer, however, produce slightly different token distributions because they compute at different precisions. Existing quantized rollout pipelines correct this mismatch with importance weights, the ratio of the trainer’s to the engine’s probability of each sampled token ([30, 44, 73]). Placing engine and trainer on one GPU also duplicates the base in memory: two bf16 copies of a 27B model exceed an 80 GB GPU, and moving the trainer’s copy to host memory (offloading) copies it every step and pins it there. As LoRA RL updates only the adapter, § 3.3 addresses both problems at their source.

Design

EpiLoRA has three parts: kernels that compute every adapter together with the low-precision base GEMM, a vLLM plugin that replaces vLLM’s LoRA layers, and an RL system on the plugin; Figure 3 compares it with existing systems.

Diagram comparing existing systems with EpiLoRA for serving and reinforcement learning

Figure 3. One low-precision base for serving and for RL. (a) Existing systems run adapters in kernels beside the FP8 GEMM, and an RL trainer keeps its own bf16 copy of the base, with importance weights correcting for the engine’s rounding. (b) EpiLoRA computes the adapters with the base GEMM, and its trainer computes on the engine’s FP8 weights, with rollouts from an FP8 engine with importance sampling or from a matched engine that needs no correction (W8A8: 8-bit weights and activations; W8A16: 16-bit activations); serving tenants from the copy RL trains works but does not pay (Appendix § 8).

Multi-adapter kernels

Fusing the adapters of many tenants into a low-precision GEMM is harder than fusing one low-rank branch that every token shares ((Li et al., 2025)), for three reasons. First, a GEMM divides its output into tiles, each computed by one thread block, and tokens of different adapters share a tile, so the epilogue must apply a different up-projection to each group of rows. Second, the kernels must quantize the activations exactly as the engine’s own GEMM does, so the base GEMM sees the same FP8 inputs. Third, during decoding the adapter path is a chain of small dependent kernels whose latency, rather than its memory traffic, sets its cost. We address the first two with two kernels, K1 and K2, that fuse the adapters’ down- and up-projections into the base GEMM, and the third with the decode path, which overlaps the down-projection with the GEMM’s main loop.

K1 and K2. K1 (Figure 4b) processes the tokens grouped by adapter, using the batching metadata vLLM already maintains. It quantizes the layer’s activations once for the base GEMM, with the same per-token scale and rounding as vLLM, and computes each adapter’s down-projection hℓ=xAℓTh_\ell= xA_\ell^{\mathsf{T}} from the bf16 activations. Because a decode batch fills only a few dozen thread blocks, K1 splits the sum over the input dimension across several blocks (split-K) to use more of the GPU. K2 then runs the low-precision GEMM over the tokens in adapter order, so that each output tile meets only a few contiguous groups of adapters. Its epilogue applies the scales and, for each adapter group in the tile, adds hℓBℓTh_\ell B_\ell^{\mathsf{T}}, reading only the tile’s slice of BℓB_\ell. Adapters may also be stored in FP8 or NVFP4 and are dequantized in registers, which halves (FP8) or nearly quarters (NVFP4) the adapter bytes the epilogue reads, though 6 of 24 accuracy checks of stored adapters failed.

Two panels comparing vLLM's four-kernel projection path with EpiLoRA's fused two-kernel path

Figure 4. Two kernels per projection instead of four. Boxes are tensors in GPU memory, arrows reads and writes. (a) vLLM 0.28 on an FP8 base, in launch order. (b) EpiLoRA’s fused path: K1 quantizes the activations once, exactly as vLLM does, and computes each adapter’s down-projection; K2 runs the FP8 or NVFP4 GEMM on tokens sorted by adapter and adds each up-projection in its epilogue.

When fusion pays. The fused path removes vLLM’s separate adapter kernels but replaces the vendor’s GEMM with ours. This pays on Ada GPUs against their default FP8 GEMM, slow during decoding; on Hopper GPUs our GEMM only matches vLLM’s CUTLASS GEMM (Table 1), and the fused path runs the down-projection first.

The decode path. During decoding, the base GEMM streams megabytes of weights, while the adapter is a chain of dependent steps: K2’s epilogue cannot add hℓBℓTh_\ell B_\ell^{\mathsf{T}} before the down-projection has finished. On the H100 this chain costs about 13 μ\mus per projection with vLLM’s tuned kernels (Table 1), although reading AℓA_\ell and BℓB_\ell takes only about a microsecond. For decode batches of up to 32 tokens, EpiLoRA therefore launches the down-projection first as a small kernel of its own, without PDL so that it starts only after xx is complete, and launches K2 as its dependent with PDL (§ 2; Figure 1c). A few extra thread blocks of this kernel, or for some shapes K2 itself, quantize the activations exactly as vLLM does. K2 starts as soon as the down-projection has begun, prefetches BℓB_\ell, streams its weights once the activations are quantized, and waits for hh only before its epilogue. Reductions are summed in a fixed order, so results are deterministic for a given batch. The same design runs on Blackwell with NVFP4 bases.

Why not a second stream? vLLM’s dual-stream mode and TensorRT-LLM also overlap the adapter with the GEMM, by running the adapter on a second CUDA stream, a kernel queue that the GPU can run concurrently (Figure 1b). Because their GEMM writes its output before the adapter’s result is added, the two streams must join after the GEMM, and a separate kernel (vLLM’s up-projection, TensorRT-LLM’s add) then reads and writes the output again. Launching the GEMM as the down-projection’s dependent avoids this join: the GEMM itself adds the up-projection in its epilogue, so the output is written once and nothing follows.

Portability. The decode path needs only PDL and a GEMM epilogue that can add a low-rank product for groups of rows, both available outside Triton, the GPU language of our kernels, so a CUTLASS-based engine could adopt it for any base format whose GEMM’s main loop streams its weights.

When the overlap hides the adapter. With the decode path, a projection’s adapter cost is approximately tepi+max⁡(0,tshrink−tmain)t_{\mathrm{epi}}+\max(0,t_{\mathrm{shrink}}-t_{\mathrm{main}}); the epilogue’s extra work plus whatever part of the down-projection outlasts K2’s main loop. At decode, tmaint_{\mathrm{main}} is the time to stream the projection’s weights, which falls with the GPU’s memory bandwidth and when tensor parallelism splits each layer’s weights across GPUs, while tshrinkt_{\mathrm{shrink}}, the latency of a small kernel, hardly changes, so the condition is easiest to meet for large projections on GPUs of modest bandwidth; Figure 5 measures where it holds. Where the down-projection lasts most of the main loop, the two kernels also compete for SMs, and a few microseconds stay exposed (§ 5). The condition therefore bounds the exposure from below: when the GEMM launches more thread blocks than the GPU has SMs, only its first blocks start before the down-projection ends, so less than the whole main loop can hide it.

Scatter plot showing exposed down-projection time against down-projection time minus the GEMM's first-wave main loop

Figure 5. What stays exposed of the down-projection under the decode path. One point per projection: Qwen3-8B layers at 1–16 decode tokens with ranks 16–128, their tensor-parallel shards and two smaller models, on four GPUs. The x axis subtracts the GEMM’s first-wave main loop, a window fitted after the data, from the down-projection’s time; dashed: the condition of § 3.1.

The vLLM plugin

EpiLoRA installs as a vLLM plugin that replaces vLLM’s LoRA linear layers; any layer it cannot serve falls back to vLLM. The kernels read vLLM’s own adapter buffers and batching metadata, so adapters are never copied, and the fused call is a registered custom operator that vLLM can capture in its CUDA graphs, which record a step’s kernel launches once and replay them to avoid launch overhead.

Timed dispatch. When the model loads, the plugin times up to three paths for each projection shape and batch size and keeps the fastest: the fused kernels, run as the decode path on Hopper GPUs; the hybrid path, the vendor GEMM followed by our adapter kernels; and the vendor GEMM followed by vLLM’s own adapter kernels. On Hopper GPUs it selects the decode path for decode batches of 8B–9B models. For larger batches, including prefill, which processes a request’s prompt, it selects the vendor GEMM with Epi-LoRA’s or vLLM’s adapter kernels, whichever is faster. On the L40S it selects the fused path (Table 1).

Memory. vLLM sizes its KV cache, which stores every request’s attention keys and values, from the memory left after the model loads, so the plugin shares scratch buffers across layers, reuses vLLM’s weight codes on NVFP4 checkpoints and stores low-precision adapters in smaller slots; with bf16 adapters, its load-time warm-up fragments memory and leaves 0.85×0.85\times vLLM’s cache on the L40S (Figure 12).

Batch-1 time per output token versus throughput with 32 rank-64 tenants

Figure 12. EpiLoRA is faster at batch 1 than every vLLM configuration and leads or ties with 32 tenants. Each point is one configuration of one job: batch-1 time per output token with one rank-64 adapter (lower is better) against throughput with 32 rank-64 tenants (higher is better), for Qwen3.5-9B (Qwen3-8B with an NVFP4 base on the Blackwell slice). Dotted lines mark the same engine without LoRA, which is also the speed of a merged adapter. On the L40S, EpiLoRA with adapters stored in NVFP4 delivers 1,767 tokens/s with 32 tenants, against 1,475 for vLLM’s best configuration in the same job (1.20×). The KV cache with 32 rank-64 slots on the L40S holds 422k tokens with vLLM, 357k with EpiLoRA’s bf16 adapters and 630k with NVFP4-stored ones; with bf16 adapters, the plugin’s load-time warm-up fragments the memory vLLM later allocates for its adapter buffers, and an opt-in deferred warm-up restores 410k against vLLM’s 422k in a job of its own. Table 2’s Qwen3-8B row on the L40S comes from one job of its own: at batch 1, 14.05 ms for vLLM’s Marlin path and 13.95 ms for EpiLoRA’s weight-only mode; with 32 tenants, 1,628 tokens/s for vLLM’s tuned path and 1,761 for EpiLoRA.

Implementation and correctness. EpiLoRA is about 6,500 lines of Python and Triton, including 48 Triton kernels and the RL trainer’s kernels, and needs no changes to vLLM. Under tensor parallelism it shards the adapters as vLLM does: projections whose outputs are split across GPUs split the up-projection’s output, and projections whose inputs are split split the down-projection’s input. We test correctness at three levels: EpiLoRA’s FP8 and NVFP4 quantization reproduces vLLM’s codes and scales bit for bit, its kernels match vLLM’s LoRA path and are bitwise reproducible, and served adapters keep vLLM’s accuracy (Figure 8).

Accuracy of vLLM's LoRA path and EpiLoRA in serving and reinforcement learning

Figure 8. EpiLoRA keeps the accuracy of vLLM’s LoRA path in serving and, with nine seeds on the 9B, of bf16 training in RL. (a) Two RL adapters of Qwen3.5-9B served by vLLM and by EpiLoRA (dots: each adapter; differences −1.0 to +1.6 points, McNemar p≥0.27p \ge0.27). (b) Accuracy after RL on OlympiadBench and Minerva (the 27B on the H100 also MATH-500), with 95% intervals over runs; bf16 training of the 27B does not fit one L40S, so its reference uses two A100s.

LoRA RL on the same base

The RL system places a trainer and a vLLM rollout engine on one GPU and trains with GRPO, a policy-gradient algorithm that compares groups of sampled responses to the same prompt ([50, 51]). After each optimizer step the trainer publishes only its adapter, which the engine loads into an adapter slot.

A trainer on the engine’s FP8 weights. A bf16 trainer differentiates a slightly different model than the FP8 engine samples from. Our FP8 trainer instead computes on the engine’s FP8 weights with bf16 activations, which removes the weight part of this mismatch and shrinks the frozen base weights from 16.7 to 10.2 GiB for a 9B model (Table 36). With one copy, the trainer maps the engine’s weights directly through CUDA’s inter-process memory sharing instead of loading its own, and the engine releases its KV cache while the trainer runs, so no weights move between phases. The trainer’s projections run a training GEMM that reads the FP8 weights in place and adds the LoRA update in its epilogue, as K2 does, so no 16-bit copy of the weights is ever made.

Table 36. Colocated LoRA RL on one GPU, one part at a time: FP8 weights let two copies fit, and one shared copy returns memory to the KV cache. Means over runs: where the trainer’s base waits during rollouts, the engine’s memory share and KV cache, seconds per step generating and training, GPU-hours per run, and final accuracy minus the block’s bf16 run with the Welch 95% half-width. aThe engine releases its KV cache while training; bone copy that keeps its KV cache. The 27B’s two-copy (6 runs) and one-copy (7 runs) rows pool every completed run; Table 31 lists the runs.

EngineTrainerCorr.WhereGiB ↓ShareKV (GiB) ↑Rollout ↓Train ↓GPU-h ↓RunsΔ acc. (points) ↑
Qwen3.5-9B on one L40S (48 GB), 200 steps
bf16bf16noneGPU16.7out of memory at every engine share tried
bf16bf16nonehost16.70.8015.3105468.4761.8%
W8A8bf16TIShost16.70.8022.784477.23−0.3 ± 4.3
W8A8FP8 (ours)TIShost10.20.8021.384477.27−1.1 ± 2.0
W8A8FP8 (ours)TISGPU10.20.455.8123459.33−3.0 ± 2.5
W8A8bFP8 (ours)TISshared0.00.5510.384447.13−0.6 ± 1.8
W8A8FP8 (ours)TISshareda0.00.8021.484457.13−2.2 ± 5.5
W8A16 (ours)FP8 (ours)noneGPU10.20.457.7114448.83−1.1 ± 2.3
W8A16 (ours)bFP8 (ours)noneshared0.00.5512.186457.35−3.4 ± 2.3
Qwen3.8-27B on one H100 (80 GB), 100 steps
bf16bf16noneGPU50.1does not fit: 101 GiB of weights
bf16bf16nonehost50.10.8512.6110334.0371.4%
W8A8FP8 (ours)TIShost27.40.8534.883393.43+0.7 ± 3.2
W8A8FP8 (ours)TISGPU27.40.485.5123354.46+0.0 ± 2.2
W8A8bFP8 (ours)TISshared0.00.6519.582353.37−0.3 ± 2.4
W8A8FP8 (ours)TISshareda0.00.8535.382353.33+0.6 ± 2.9

Matched-precision rollouts. To first order, the engine’s and the trainer’s log-probabilities of a sampled token yy differ by three terms, from rounding the weights, rounding the activations, and differences between the two sides’ kernels:

log⁡πeng(y)−log⁡πtr(y)≈δW+δA+δK.(1)\log\pi_{\mathrm{eng}}(y) - \log\pi_{\mathrm{tr}}(y) \approx\delta_W + \delta_A + \delta_K . \tag*{(1)}

The FP8 trainer removes δW\delta_W, but not δA\delta_A, the engine’s rounding of each GEMM’s input activations. Importance corrections compensate for this difference (Liu et al., [30]); we remove it in the arithmetic instead. In the matched engine, every FP8 layer computes with 8-bit weights and 16-bit activations (W8A16) instead of rounding both to 8 bits (W8A8) as vLLM’s FP8 engine does, using the trainer’s arithmetic on the same weights: FP8 weights widened to bf16 in registers, bf16 activations and fp32 accumulation. vLLM’s weight-only FP8 path, Marlin (Frantar et al., [7]), also leaves activations unrounded but repacks the weights into a layout the trainer cannot share.

Evaluation

We ask what the kernels save (§ 5), what that buys end to end (§ 5.1), and whether the same base trains as well as bf16 with less memory (§ 5.2).

Setup. We run vLLM 0.28 with CUDA graphs and PyTorch’s compiler, kernels cached before we measure. We serve Qwen3-8B (Qwen Team, [45]), Llama-3.1-8B (Llama Team, AI @ Meta, [31]), Qwen3.5-9B (Qwen Team, [46]), which replaces most attention layers with linear attention, and Qwen2.5-72B, with FP8 bases that vLLM quantizes when the model loads, and our NVFP4 checkpoint of Qwen3-8B; RL trains Qwen3.5-9B and Qwen3.8-27B ([47]). The GPUs are the L40S (Ada), the H100, H200 and GH200 (Hopper), and a 48 GB partition of an RTX PRO 6000 (Blackwell), which we call the Blackwell slice. For serving, the baselines are vLLM’s LoRA path on its fastest base for each GPU, with LoRA kernels we tuned for each model, SGLang ([72]) and TensorRT-LLM ((NVIDIA, 2026)). For RL, they are FlashRL’s FP8 engine beside a bf16 trainer with truncated importance sampling (TIS), which multiplies each token’s gradient by its importance weight capped at 2 ([30]), bf16 training with the trainer’s weights offloaded during rollouts ([51]), and one bf16 copy shared by engine and trainer, as in Unsloth ([55]) but keeping its KV cache. We measure latency as the time per output token (TPOT) of a single request, and throughput with 256 requests of 512 prompt and 256 output tokens, using random adapters of rank 16–128, since speed depends only on the adapters’ shapes. Each engine is started four times and we report the mean of the last three unless a table says otherwise; the appendices give every measurement. Kernel timings use CUDA graphs with the L2 cache flushed; unless noted, adapters have rank 64, and bold in tables marks the best value, with ties within 2%.

Kernels

The adapter’s cost falls by 2–6× on every GPU (Table 1). On Hopper GPUs, one rank-64 adapter costs 53–57 µs per layer with vLLM’s tuned kernels and 10–12 µs with the decode path, so the layer takes 0.70–0.75× vLLM’s time. On the H100, nine tenths of the saving is in the adapter and a tenth in EpiLoRA’s own GEMM. The overlap pays because the GEMM needs the down-projection only in its epilogue, so it streams its weights while the adapter is computed; vLLM’s dual-stream mode overlaps the adapter only with the GEMM as a whole (Figure 1b). On the L40S, fusing adapters into EpiLoRA’s own GEMM halves their cost, and

on Blackwell the decode path cuts it from 54 to 17 µs.

What stays exposed follows the overlap condition (Figure 5). The measured exposure tracks the condition with a window we fitted after the data: the down-projection stays hidden while it finishes within the main loop of the GEMM’s first wave of thread blocks, when the first epilogues need it. With a rank-64 adapter on full-size layers, the down-projection ends within the GEMM’s main loop on every projection we measured, and at most 2.1 µs of it remains exposed. At ranks 16 and 128 and on tensor-parallel shards, up to 9 µs per projection stays exposed, mostly where the GEMM launches more blocks than the GPU has SMs.

The lead holds from 1 to 16 decode tokens (Table 4). With the decode path, one adapter adds 4–9% to the layer on Hopper GPUs, against 35–55% with vLLM’s tuned kernels or dual-stream mode, because the down-projection finishes within the GEMM’s main loop (Figure 5); run serially, the layer takes 1.13–1.31× as long, depending on the down-projection kernel (Table 11, Table 12), so at one decode token the overlap accounts for a third to two thirds of the saving.

Table 4. From 1 to 16 decode tokens, the decode path keeps one adapter’s cost to a few percent of the layer. One Qwen3-8B decoder layer, in microseconds; cost: the time over the layer without an adapter (vLLM: the better of its tuned kernels and dual-stream mode).

μs ↓No adaptervLLMEpiLoRACost (%) ↓
TokenstuneddualserialoverlapvLLMEpiLoRA
H100, FP8 base
1128179180163134426
4128179172162133354
16128180174167136367
H200, FP8 base
1104161167144112558
4106162157147112486
16106162159150115499

Table 4.

Table 11. The overlap beats serializing even a library-fast down-projection, and comes within 2% of a serialized kernel that does no work (0.99–1.02×\times). One Qwen3-8B decoder layer’s four projections at one decode token with one adapter, microseconds summed over the projections (CUDA graphs, L2 cache flushed), on one H200 and one H100. Serialized: a kernel run before the same GEMM without overlap, which does no work (empty kernel), uses cuBLAS’s GEMM or GEMV, or is our own down-projection. GEMM, hh given: the GEMM with the down-projection’s result already in memory, a lower bound for any overlap. Shrink alone: the down-projection by itself.

µs ↓SerializedGEMM, hh givenShrink aloneEmpty
GPURankDecode pathempty kernelcuBLAS GEMMcuBLAS GEMVourscuBLASourskernel
H20016108.1110.2135.0134.6138.9106.037.648.612.8
64112.4114.0140.1139.6144.6110.438.650.313.3
128117.9120.7146.1146.3150.8117.340.551.712.9
H10016129.3132.0154.9154.5156.8128.742.049.420.3
64133.4134.8158.0158.5163.0130.942.153.419.6
128139.2138.2163.5163.0164.8135.344.652.219.9

Table 11.

GPUrankdecode pathserializedratioper projectionshrink alone
µs ↓µs ↓its splitfastest
H20016109.6118.31.08×\times1.05–1.12×\times11.1–14.66.1–8.6
H20064112.1130.81.17×\times1.11–1.24×\times9.0–17.86.6–9.0
H200128117.9146.11.24×\times1.17–1.33×\times9.7–18.26.9–9.7
H10016128.8140.31.09×\times1.06–1.15×\times9.3–15.27.4–9.7
H10064133.1149.91.13×\times1.09–1.19×\times10.1–18.17.5–9.9
H100128137.9162.01.17×\times1.11–1.26×\times10.6–18.27.9–10.5

Table 12.

End-to-end serving

At batch 1, EpiLoRA is faster than every system we ran on Hopper GPUs (Table 2 and Table 3). For 8B–9B models it takes 0.76–0.88× the time per token of vLLM’s fastest configuration, including Llama-3.1-8B with both systems’ kernels tuned for its shapes. SGLang and TensorRT-LLM take 1.4–1.8× EpiLoRA’s time on Qwen3-8B. Profiles show why (Table 3): per token on the H100, their adapter kernels add 2.9–3.0 ms of GPU time, vLLM’s dual-stream mode 1.9 and EpiLoRA’s 0.4, leaving EpiLoRA 9–14% slower than without LoRA (Table 18).

Table 2. EpiLoRA against vLLM’s fastest configuration. Time per output token with one adapter at batch 1, and throughput with 32 tenants; vLLM runs its fastest base (NVFP4 on the Blackwell slice, FP8 elsewhere) with LoRA kernels tuned for each model (Qwen3.5-9B’s on the H100 for all its Hopper rows). On 2×H100, both systems use tiles searched for the shards; the H200 72B row comes from an earlier job than Table 29, which gives 0.94×.

GPUModelvLLMEpiLoRARatio
H100Qwen3.5-9B6.265.140.82×
H100Qwen3-8B6.024.810.80×
H100Llama-3.1-8B5.594.760.85×
H200Qwen3.5-9B6.114.810.79×
H200Qwen3-8B5.944.510.76×
GH200Qwen3.5-9B6.705.910.88×
H200Qwen2.5-72B28.0026.800.96×
2×H100Qwen2.5-72B20.3117.750.87×
L40SQwen3.5-9B15.3914.970.97×
L40SQwen3-8B14.0513.950.99×
BlackwellQwen3-8B10.279.460.92×
32 tenants, thousand tokens per second ↑
H100Qwen3.5-9B5.305.641.06×
H100Qwen3-8B5.896.221.05×
H100Llama-3.1-8B6.266.321.01×
H200Qwen3.5-9B5.295.781.09×
H200Qwen3-8B5.836.161.06×
GH200Qwen3.5-9B5.576.001.08×
L40SQwen3.5-9B1.471.591.08×
L40SQwen3-8B1.631.761.08×
BlackwellQwen3-8B2.082.111.02×

Table 2.

Table 3. EpiLoRA against three serving systems on Qwen3-8B, each pair measured in one job. Time per output token at batch 1 and throughput with 32 tenants, relative to EpiLoRA’s, each system in its fastest LoRA configuration (TRT-LLM: TensorRT-LLM’s PyTorch backend). ^aAt its fastest KV-cache share, 0.6. ^bWithout LoRA, 1.09–1.14× vLLM’s batch-1 time and 0.95× its throughput; H200: one start. ^cFrom NVIDIA’s Nsight Systems profiler (Table 21); vLLM in dual-stream mode with its default kernels.

EpiLoRAvLLMSGLang^bTRT-LLM
Batch 1: time per token relative to EpiLoRA’s ↓
H1001.00×1.25×1.65×1.43×
H2001.00×1.32×1.81×1.52×
Batch 1: GPU time the adapter adds per token (ms)^c ↓
H1000.401.873.012.87
32 tenants: throughput relative to EpiLoRA’s ↑
H1001.00×0.95×0.94×0.97×^a
H2001.00×0.95×0.93×1.05×

Table 3.

Engine, LoRA pathW1 (ms) ↓W2 (tok/s) ↑W1 ↓W2 ↑W1 ↓W2 ↑
1 adapter (%)32 adapters (%)
SGLang 0.5.9, csgmv4.9110,125+64.9−43.8+64.8−43.7
SGLang 0.5.9, triton4.9110,125+67.4−77.2+66.6−77.2
vLLM 0.28, default kernels4.5210,663+52.1−27.5+83.5−54.3
vLLM 0.28, dual-stream4.5210,663+39.3−30.6+67.8−54.4
ours, decode path4.5210,663+8.6−28.1+8.1−43.4

Table 18. On the H100, EpiLoRA has 0.61× SGLang’s batch-1 latency and 1.06× its throughput with 32 tenants. Qwen3-8B with an FP8 base, rank-64 adapters; each engine’s W1 (time per output token at batch 1) and W2 (throughput) without LoRA, and each LoRA path’s change against its own engine; means of three starts. SGLang ran with CUDA graphs up to batch 256 and as many LoRA slots as adapters; its LoRA path adds about the same time per step with one adapter or 32 (3.2 ms at batch 1).

The lead is largest on the H200, whose faster memory shortens the base GEMM so the adapter is a larger share of each step, and smallest on the GH200, where both systems run slower than on the H100. On the L40S, where vLLM is fastest with Marlin’s weight-only GEMM, EpiLoRA’s weight-only mode (bf16 activations) matches it; on the Blackwell slice’s NVFP4 base, it takes 0.92× vLLM’s time.

At batch 1, the lead extends to a 72B model (Table 2). A larger model leaves less to save, as its GEMMs grow with the square of the hidden size and the adapter linearly: Qwen2.5-72B takes 0.96× the time per token of tuned vLLM on one H200 and 0.87× across two H100s.

With many tenants, EpiLoRA keeps more of the base’s throughput (Table 2). With 32 rank-64 tenants it delivers 1.05–1.09× the throughput of vLLM’s best configuration on 7 of the 9 GPU–model pairs. These batches exceed the 32 tokens the decode path covers, so the gain comes from EpiLoRA’s adapter kernels after the vendor GEMM: on the H200, swapping in vLLM’s turns 1.10× into 0.97× in one job (Table 14). They beat vLLM’s on projections with three or four outputs of unequal widths, such as attention’s query, key and value, where vLLM’s up-projection costs 1.7× as much per output column (Table 15); at 32 tokens,

Projection1–32 tokens64–2,048 tokens64128256512
linear attention, input (4 outputs)decode pathEpiLoRA’s kernels1.311.171.621.57
attention, query/key/value (3 outputs)decode pathEpiLoRA’s kernels1.451.251.781.89
MLP, gate/up (2 outputs)decode pathvLLM’s kernels; EpiLoRA’s kernels at 2560.870.891.000.99
attention outputdecode pathvLLM’s kernels0.810.720.840.70
MLP, downdecode pathvLLM’s kernels0.840.740.890.78

Table 14. Above 32 tokens, EpiLoRA’s adapter kernels win on the projections that combine three or four outputs, and the dispatch keeps vLLM’s kernels for the rest. The timed dispatch’s choice per projection of Qwen3.5-9B on the H100 with 32 rank-64 adapter slots (FP8 base, CUDA-graph replays, one adapter), and the projection’s time with vLLM’s adapter kernels over its time with EpiLoRA’s at the given batch size, the same vendor GEMM included in both (above 1: EpiLoRA’s are faster; our call of vLLM’s kernels zero-fills their buffer, at most 0.5 MB here, once more than vLLM does). The default-capture serving configuration’s dispatch picks the same paths, and so does the dispatch on an H200, where this ratio is 1.19–2.00 on the projections with three or four outputs and the gate/up projection switches to them from 256 tokens. With 32 rank-64 tenants on the H200, EpiLoRA delivers 5,813 tokens/s against vLLM’s 5,286 (1.10×); with vLLM’s adapter kernels above 32 tokens instead of its own, it delivers 5,148 (0.97×), so the gain comes from EpiLoRA’s kernels above 32 tokens (one job, three passes each).

Outputs (projections)ColumnsvLLMEpiLoRAvLLMEpiLoRA
3–4 unequal (32)376,8328.144.7121.612.5
gate/up, 2 equal (32)786,43210.549.5213.412.1
one output (64)262,1442.083.427.913.1

Table 15. vLLM’s up-projection costs more per output column as a projection’s outputs multiply; EpiLoRA’s costs about the same on every kind. The up-projection’s GPU time per kind of LoRA projection of Qwen3.5-9B in the profiled H100 step of Table 27, with vLLM’s kernel tuned for these shapes and with EpiLoRA’s expand-add. Each kind’s time is a difference between profiles whose timed dispatches send it to different kernels: vLLM sends all 152 projections to its kernel (20.91 ms); EpiLoRA sends the 32 with unequal outputs to its own (4.71 ms, with 12.77 left on vLLM’s), with the new tiles gate/up as well (14.23; 2.23 left; a profile of the same step that Table 27 does not show), and with its own kernels all but the linear attention’s 24 two-column projections (17.65; 0.15 left).

the lead per layer shrinks as a batch mixes more distinct adapters (Figure 11). On the L40S the gain comes from fusing adapters into EpiLoRA’s GEMM; TensorRT-LLM beats EpiLoRA on the H200 (Table 3).

Relative layer time versus no LoRA for distinct adapters on L40S and H100

Figure 11. EpiLoRA’s advantage is largest with few distinct adapters. Time of one Qwen3-8B layer with rank-64 adapters at 32 decode tokens, relative to vLLM’s FP8 GEMM without adapters, as the number of distinct adapters in the batch grows. (a) L40S, fused path, with adapters in bf16, FP8 or NVFP4; (b) H100, decode path. vLLM tuned: LoRA kernels tuned for the GPU.

Online, EpiLoRA lowers the time per output token at most loads (Figure 6). When CUDA graphs cover the prefill sizes, its median time per output token with 32 tenants is 0.77–0.95× vLLM’s on the H100 and 0.92–0.99× on the L40S, and its 99th-percentile (p99) time to first token (TTFT) stays within 1.1× of vLLM’s at 6 of these 8 GPU–rate points. On a WildChat trace, EpiLoRA takes 0.70–0.86× vLLM’s time per output token on the H200 and 0.77–0.98× on the H100 at vLLM’s defaults, and serves at least vLLM’s goodput at every rate (Table 5). Its lead is largest at low load, where batches fit the decode path.

Online EpiLoRA performance comparison on L40S and H100

Figure 6. Online, EpiLoRA lowers the time per output token at most loads. Median time per output token (top) and p99 time to first token (bottom, log scale) with 32 tenants, relative to vLLM with tuned kernels. Solid: CUDA graphs captured up to 2,048 tokens; dotted: vLLM’s default capture, up to 512. Qwen3.5-9B, Poisson arrivals, 512 input and 256 output tokens; bars: range over three starts.

Req/svLLMEpiLoRAvLLMEpiLoRAvLLMEpiLoRA
Median TPOT (ms) ↓p99 TTFT (s) ↓Goodput (req/s) ↑
H200
29.17.00.090.091.981.99
412.88.90.100.093.883.92
818.014.60.120.117.267.43
1630.626.40.160.1612.0812.70
H100
29.97.60.160.181.981.99
414.311.10.180.193.873.91
821.819.01.3710.227.157.35
1633.132.40.240.2712.0012.38

Table 5.

At vLLM’s default graph sizes (dotted), prefills longer than 512 tokens run outside CUDA graphs, where every kernel

Table 5. On a real trace, EpiLoRA lowers the time per output token at every load. Per start, 1,000 of 3,383 first turns of WildChat conversations (short prompts: median 23 tokens) with gamma arrivals (burstiness 0.5), 32 tenants, Qwen3.5-9B at vLLM’s default CUDA-graph settings; goodput counts requests meeting a 2 s TTFT and a 50 ms TPOT. 2One of vLLM’s three starts took 3.7 s (the others 0.19–0.20 s), so neither value is bold.

:::

launch costs host time: EpiLoRA then trails vLLM slightly on the L40S at 4–6 requests/s and by 1.18× on the H100 at 16 requests/s, and its p99 TTFT is 1.1–1.2× vLLM’s on the H100; Table 24 and Table 25 add the H200 and long prompts.

GPURateMedian TPOT (ms) ↓p99 TTFT (ms) ↓
vLLMEpiLoRARatiovLLMEpiLoRARatio
H10029.97.90.792132541.20
415.513.00.842382741.15
828.929.01.002683051.14
1654.864.71.183604001.11
H100, new tiles29.97.70.782142421.13
415.512.50.812432621.08
829.027.70.952692891.08
1654.961.11.113613861.07
H100, new tiles, own kernels29.97.50.762142131.00
415.511.80.762432400.99
829.024.90.862692660.99
1654.958.61.073613861.07
H20028.86.50.74961111.17
412.08.90.741201381.14
821.217.60.831661640.99
1646.643.10.932702430.90
L40S123.721.90.932352411.03
232.031.10.972552621.03
451.452.21.023323391.02
686.988.51.023,0624,0541.32

Table 24. Online serving at vLLM’s default CUDA-graph sizes. Qwen3.5-9B with an FP8 base, 32 rank-64 tenants, Poisson arrivals at the given rate (requests per second), 512-token prompts and 256 output tokens, 1,000 requests per rate; vLLM with LoRA kernels tuned for this model (on the H200, the H100’s tuning) against EpiLoRA (with direct launches on the H100), both at vLLM’s default capture up to 512 tokens. New tiles: down-projection tiles searched for batches above 512 tokens; own kernels: no vLLM adapter kernels in steps above the capture bound; both opt-in, each in a job of its own with vLLM measured again. Means of three server starts; ratios are EpiLoRA’s over vLLM’s.

GPURequests/svLLMoursratiovLLMoursratio
Median TPOT (ms) ↓p99 TTFT (ms) ↓
Qwen3.5-9B, 32 tenants, 2,048-token prompts
H20018.005.880.731751730.99
29.797.080.722352100.90
416.7011.090.663382770.82
H10018.086.410.792422501.03
210.027.840.782952971.01
416.8913.040.774023840.95
Qwen2.5-72B, 8 tenants, 512-token prompts
H2000.535.7134.750.974224160.99
141.8439.700.955415050.93
259.3054.570.927036940.99
Qwen2.5-72B at TP2 on two H100s, vLLM tuned for the shards
H1000.523.9120.780.872192491.14
126.1423.260.892722801.03
231.6628.570.906413680.57

Table 25. At vLLM’s default CUDA-graph sizes, EpiLoRA lowers the time per output token with 2,048-token prompts and on a 72B model. Top: Qwen3.5-9B with an FP8 base, 32 rank-64 tenants, Poisson arrivals, 2,048-token prompts and 256 output tokens. Middle: Qwen2.5-72B-Instruct on one H200 with 8 tenants and 512-token prompts. Bottom: the 72B across two H100s with tensor parallelism and 8 tenants, against vLLM’s kernels tuned for the shards. vLLM with tuned LoRA kernels against EpiLoRA with direct launches; means of three server starts, whose time-per-token ratios agree within 0.03.

LoRA RL on the same base

GRPO (Shao et al. [50]) trains a rank-64 adapter on competition math (DAPO-Math (Yu et al. (2025)) with engine and trainer on one GPU: Qwen3.5-9B for 200 steps of 16 problems and Qwen3.8-27B for 100 steps of 8 problems, 8 samples each, with samples and answers capped at 2,048 tokens. We report accuracy on OlympiadBench (He et al. (2024)) and Minerva (Lewkowycz et al. [22]), with 3–9 seeds and 95% intervals (Table 30, Table 31); RL lifts the 9B from 50.5% to 60–61% (Figure 8b).

CodeEngineTrainer, correctionGPURunsGPU-h ↓Accuracy (%) pre-RLAccuracy (%) final [95% CI]Δ (points) ↑≤ pre-RL
S0Gbf16bf16H100a56.949.8–50.557.4–61.8reference0/5
S0Tbf16, asleepbf16H10035.650.561.0–61.7+1.5 [−0.4, +3.5]0/3
S8GW8A8FP8 (deq.)H10055.650.6–51.52.3–60.7−12.9 [−43.9, +18.0]1/5
S4FW8A8bf16H10035.349.7–51.04.3–52.4−32.7 [−92.4, +27.0]2/3
S8TW8A8FP8 (deq.), TISH10045.750.6–51.057.6–61.0−0.4 [−2.8, +2.1]0/4
S0TFbf16, asleepbf16 + FLAH10093.249.8–50.558.2–63.5+0.9 [−1.2, +2.9]0/9
S8CW8A8FP8 (ours), centeringH10044.049.9–50.657.1–62.8+0.7 [−2.9, +4.4]0/4
S8WW8A16, MarlinFP8 (ours)H10043.949.5–50.458.9–62.1+0.3 [−2.2, +2.7]0/4
S8TLW8A8FP8 (deq.), TISL40S313.449.9–50.560.5–60.9+1.0 [−1.0, +2.9]0/3
S8MLW8A16, matchedFP8 (ours)L40S38.850.2–50.660.0–62.0+0.9 [−1.5, +3.3]0/3
S8WAW8A16, MarlinFP8 (deq.) + FLAA10086.149.2–50.658.8–63.4+0.8 [−1.3, +2.8]0/8
S9PW8A8FP8 (deq.)H10035.750.7–51.556.2–58.5−2.3 [−4.8, +0.1]0/3
S9BW8A8FP8 (deq.)H10035.849.6–50.247.7–59.7−6.3 [−20.4, +7.8]1/3
S8TFW8A8FP8 (ours), TISH10093.149.4–51.057.7–62.1−0.3 [−2.0, +1.3]0/9
S4TFW8A8bf16 + FLA, TISH10093.050.2–51.060.1–62.8+0.6 [−0.9, +2.0]0/9
S8MHW8A16, matchedFP8 (ours)H10094.250.7–50.858.2–64.4+0.1 [−1.6, +1.9]0/9
S8UFW8A8FP8 (ours)H10063.149.9–51.554.2–57.2−4.9 [−6.5, −3.3]0/6
S8TLFW8A8FP8 (ours), TISL40S39.349.6–50.557.7–60.2−2.0 [−5.1, +1.1]0/3
S0OLbf16bf16 + FLAL40S78.449.6–51.159.9–64.8reference0/7
S8OLW8A8FP8 (ours), TISL40S77.249.5–50.758.1–62.4−1.1 [−3.1, +0.9]0/7
S4OLW8A8bf16 + FLA, TISL40S37.250.2–50.859.2–63.6−0.3 [−4.6, +4.1]0/3
S8RAW8A8FP8 W8A8 (ours)L40S38.249.6–51.351.1–60.5−6.5 [−17.5, +4.5]1/3
S8ULFW8A8FP8 (ours)L40S38.549.5–50.852.5–60.1−6.3 [−15.4, +2.8]0/3

Table 30. Every LoRA-RL configuration on Qwen3.5-9B: its configuration, cost and accuracy against its reference (continued in Table 31). Arms grouped by setting, each group named with its reference. Accuracy: OlympiadBench and Minerva pooled, greedy, each policy on its own engine. Engines: W8A8, vLLM’s FP8 engine with EpiLoRA; W8A16, vLLM’s weight-only FP8 engine (Marlin) or EpiLoRA’s matched engine; asleep, sleep mode during training. Trainers: bf16, or on the engine’s FP8 weights, dequantized on every call (deq.) or through our kernels (ours, with flash-linear-attention, FLA). Δ: final accuracy minus the reference’s, Welch 95% interval; ≤ pre-RL: runs that ended at or below their accuracy before RL.

Table 31. Every LoRA-RL configuration (continued from Table 30): the 27B, one copy shared by the engine and the trainer, and Qwen3-4B. Columns as in Table 30; each block’s header names its benchmarks and, for the 27B, the accuracy before RL. The 27B is scored on the bf16 engine (on the L40S also on OlympiadBench and Minerva only, as the 9B), except where a header says otherwise. One FP8 copy of the 27B with its KV cache kept ran out of memory on the L40S.

CodeEngineTrainer, correctionGPURunsGPU-h ↓Accuracy (%) final [95% CI]Δ (points) ↑≤ pre-RL
Qwen3.8-27B math, MATH-500, OlympiadBench and Minerva (before RL 61.6–62.8%); against Q27
Q27bf16bf162×A100315.770.8–74.6reference0/3
S8T1gW8A16, MarlinFP8 (deq.), TISA100315.371.9–73.3−0.4 [−4.4, +3.6]0/3
S8T27HW8A8FP8 (ours), TISH1002 + 1ᵇ4.472.4–73.5+0.2 [−3.9, +4.2]0/2
S0T27Abf16, offloadedbf16 + FLAA10037.371.2–72.0−1.2 [−5.6, +3.1]ᶜ0/3
S8T27AW8A16, Marlin, offloadedFP8 (deq.) + FLA, TISA10036.570.6–72.0−1.3 [−5.3, +2.7]ᶜ0/3
S8S27HW8A8FP8 (ours), one copy, TISH10053.368.1–72.6−2.5 [−6.2, +1.2]0/5
Qwen3.8-27B math on the H100, the same benchmarks; against S0T27HO
S0T27HObf16, offloadedbf16 + FLAH10034.070.4–72.6reference0/3
S8T27HOW8A8, offloadedFP8 (ours), TISH10033.470.6–73.7+0.7 [−2.5, +3.9]0/3
S4T27HOW8A8, offloadedbf16 + FLA, TISH10033.270.7–72.4−0.1 [−2.4, +2.3]0/3
Qwen3.8-27B math on the H100, the same benchmarks, one copy, KV cache released for training; against S8T27HO
S8K27HW8A8FP8 (ours), one copy, TISH10033.370.9–73.6−0.1 [−3.5, +3.3]0/3
Qwen3.8-27B math on one L40S, OlympiadBench and Minerva (before RL 49.7–51.1%); against Q27 on the same benchmarks
Q27bf16bf162×A100315.759.7–63.7reference0/3
S8T27LW8A8, offloadedFP8 (ours), TISL40S311.558.7–63.0−0.9 [−5.7, +3.8]0/3
Qwen3.8-27B math on one L40S, the same benchmarks on each run’s own FP8 engine, one copy, KV cache released; against S8T27L
S8K27LW8A8FP8 (ours), one copy, TISL40S311.257.8–61.1+0.0 [−3.9, +3.9]0/3
Qwen3.5-9B math on the L40S, one copy; against S8TLF (two copies)
S8S9LW8A8FP8 (ours), one copy, TISL40S37.160.4–61.7+2.4 [−0.2, +5.1]0/3
Qwen3.5-9B math on the L40S, one copy, matched engine; against S8ML (two copies)
S8MS9LW8A16, matchedFP8 (ours), one copyL40S57.356.1–60.1−2.3 [−4.7, +0.1]0/5
Qwen3.5-9B math on the L40S, one copy, KV cache released for training; against S8OL
S8K9LW8A8FP8 (ours), one copy, TISL40S37.156.6–61.8−0.3 [−5.8, +5.1]0/3
Qwen3-4B math; against S0L
S0Lbf16bf16L40S34.549.7–50.2reference0/3
S8LW8A8FP8 (deq.)L40S33.649.7–51.4+0.7 [−1.3, +2.7]0/3

Table 31. ᵇ A run that stopped early and was not resumed (seed 2: out of memory at step 88). ᶜ No reference run.

Matching the weights removes the largest source of mismatch, and leaving activations unrounded removes most of the rest (Figure 7). We measure the mismatch as the KL divergence between the engine’s and the trainer’s next-token distributions. Rounding the weights raises it the most, to 12–21× that of a bf16 engine and trainer, and the FP8 trainer removes this part by computing on the engine’s own weights. Almost all of the rest comes from rounding activations: engines that round them retain 6–14×, while our matched engine and vLLM’s Marlin retain 1.0–1.4×, and the order holds on a dense second model (Table 33).

Per-token KL divergence between engine and trainer configurations

Figure 7. With the weights matched, rounded activations cause most of the remaining mismatch. Per-token KL from engine to trainer relative to a bf16 engine and trainer, five 9B adapters on the H100 (bars: geometric mean; whiskers: range); the trainer computes on the engine’s FP8 weights unless marked bf16, and hatched bars change one component of the bar above. Earlier, smaller decompositions measured up to 2.1× for the unrounded engines.

AdapterW8A8matched W8A16Marlin W8A16shareover W16 trainer
bf16-trained, seed 06.47×1.35×1.34×0.761.35×
bf16-trained, seed 16.80×1.20×1.49×0.741.36×
bf16-trained, seed 27.41×1.51×1.60×0.861.50×
W8A8-trained, seed 06.31×1.14×1.30×0.791.49×
W8A8-trained, seed 17.16×1.38×1.30×0.761.37×

Table 33. On a second, dense model the decomposition’s ordering holds. Qwen3-4B, five math RL adapters (bf16- or W8A8-trained), 128 DAPO-Math prompts × 4 samples on one H100: each engine’s per-token KL to the weight-matched trainer over that of a bf16 engine and trainer; the fake-quant control’s share of the W8A8 engine’s excess; and a trainer that rounds its activations as the engine does, over the W8A16 trainer. KL over the bf16 pair’s ↓; Fake-quant share; Rounded alike over W16 trainer.

On the 9B, corrected or matched FP8 RL ends where bf16 training does (Figure 8b). We call two configurations equivalent when the 95% interval of their difference lies within ±5 points, about half of RL’s gain. With nine seeds, W8A8 rollouts with TIS, FlashRL’s recipe and the matched engine without any correction end within 0.6 points of bf16 training, every interval within ±2.6 points (Table 32), while uncorrected W8A8 rollouts end 4.9 points below it; on an L40S, the matched engine with one copy that keeps its KV cache ended 3.4 points below bf16 training ([−5.7, −1.1]), but against two copies one copy moved it by −2.3 and W8A8 rollouts with TIS by +2.4 (Welch intervals spanning zero, paired ones excluding it in opposite directions), so the one-copy effect is inconclusive (Appendix § 8). The matched engine needs no correction because it leaves the mismatch at the bf16 pair’s level (Figure 7) and keeps it there throughout training (Table 34). On the 27B, EpiLoRA’s configurations end at 72.0–72.1% (with MATH-500) against 71.4% for bf16 training and FlashRL, every interval within ±4 points with three seeds (Table 31).

Table 32. Equivalence of the RL configurations with their references, three ways. Final accuracy (OlympiadBench and Minerva pooled) of each configuration minus its reference: on each policy’s own engine, the Welch 95% interval over all runs and the tt interval of the per-seed differences over the seeds both ran (a seed fixes the problem order); and the Welch interval with every policy re-scored on one common bf16 engine (avg@8). p≠0p_{\ne0}: Welch’s test of a zero difference; p±5p_{\pm5}: the two one-sided tests of the ±5-point margin; both Holm-corrected over these twelve comparisons (a family chosen after the data). Runs: the configuration’s and the reference’s.

ComparisonGPURunsEach policy’s own engine: Welch 95% CIEach policy’s own engine: p≠0p_{\ne0}Each policy’s own engine: p±5p_{\pm5}Paired by seed: SeedsPaired by seed: intervalOne bf16 engine, avg@8: Welch 95% CIOne bf16 engine, avg@8: p≠0p_{\ne0}One bf16 engine, avg@8: p±5p_{\pm5}
W8A8 + TIS against tuned bf16H1009/9−0.3 [−2.0, +1.3]1.000<0.0019−0.3 [−2.6, +1.9]−0.3 [−1.6, +1.1]1.000<0.001
FlashRL’s recipe against tuned bf16H1009/9+0.6 [−0.9, +2.0]1.000<0.0019+0.6 [−1.3, +2.4]+0.5 [−0.6, +1.6]1.000<0.001
matched engine against tuned bf16H1009/9+0.1 [−1.6, +1.9]1.000<0.0019+0.1 [−1.3, +1.5]+0.1 [−1.4, +1.7]1.000<0.001
W8A8 + TIS, offloaded, against bf16 offloadedL40S7/7−1.1 [−3.1, +0.9]1.0000.0057−1.1 [−3.5, +1.3]−0.3 [−2.0, +1.5]1.000<0.001
FlashRL’s recipe, offloaded, against bf16 offloadedL40S3/7−5.3 [−4.6, +4.1]1.0000.1063−1.2 [−8.6, +6.6]−0.1 [−3.8, +3.6]1.0000.046
one copy against two copies (W8A8 + TIS)L40S3/3+2.4 [−0.2, +5.1]0.6010.1083+2.4 [+0.3, +4.5]+2.1 [−0.3, +4.4]0.7480.046
one copy against two copies (matched)L40S5/3−2.3 [−4.7, +0.1]0.6010.1063−2.8 [−5.3, −0.3]−2.3 [−5.3, +0.7]0.9020.046
one copy, KV released, against offloading (W8A8 + TIS)L40S3/7−1.1 [−6.8, +4.6]1.0000.1513−0.3 [−4.2, +3.6]−1.6 [−4.5, +1.3]1.0000.046
two copies (W8A8 + TIS) against bf16 offloadedL40S3/7−3.0 [−5.5, −0.5]0.3050.1513−3.9 [−11.3, +3.5]−1.6 [−3.8, +0.6]1.0000.026
one copy (W8A8 + TIS) against bf16 offloadedL40S3/7−0.6 [−2.4, +1.3]1.0000.0023−1.5 [−9.1, +6.1]+0.5 [−1.1, +2.0]1.000<0.001
two copies (matched) against bf16 offloadedL40S3/7−1.1 [−3.3, +1.2]1.0000.0173−2.0 [−6.5, +2.5]+0.4 [−2.4, +3.1]1.0000.026
one copy (matched) against bf16 offloadedL40S5/7−3.4 [−5.7, −1.1]0.1080.1515−3.6 [−5.8, −1.5]−1.9 [−3.5, −0.3]0.3040.006

Table 32.

ConfigurationRuns1–1091–110181–200Late/early ↓
bf16 engine and trainer90.00550.00610.00641.18×
FlashRL’s recipe (W8A8, bf16 trainer, TIS)90.02720.03080.03481.28×
W8A8, FP8 trainer, TIS90.01730.01920.02201.27×
matched engine, no correction90.00700.00680.00751.07×
W8A8, FP8 trainer, no correction60.01740.02440.03942.26×

Table 34. Without correction, the matched engine’s mismatch stays at the bf16 level over training; W8A8 rollouts’ grows. Qwen3.5-9B on the H100, the nine-seed configurations of Figure 13 and uncorrected W8A8 rollouts; mean absolute difference between the trainer’s and the engine’s log-probabilities of the sampled tokens, averaged over each window of steps and then over runs (descriptive, from the runs’ step logs). TIS clipped 0.08% of the tokens’ importance weights in the first ten steps and 0.16% in the last twenty with FlashRL’s recipe, and 0.01% and 0.06% with the FP8 trainer (at most 0.31% in any step). Mismatch, mean ∣Δlog⁡p∣|\Delta\log p|, at steps (↓).

An FP8 trainer fits the 27B on one 48 GB GPU. Training on the engine’s FP8 weights saves 6.5 GiB for the 9B and 22.7 GiB for the 27B (Table 36). So the 27B trains on one 48 GB L40S, which its bf16 weights (50 GiB) exceed: with the trainer’s copy offloaded, EpiLoRA reaches 60.8%, against 61.7% for bf16 training on two A100s (Figure 8b).

With long rollouts, one FP8 copy comes close to the fastest placement with a fraction of its host memory (Table 6 and Table 7). With rollouts of up to 8,192 tokens, two FP8 copies fit neither model, and one bf16 copy that keeps its KV cache fits only the 9B, at 2.2×2.2\times the step time (1.9×1.9\times that of an FP8 copy that also keeps it, Table 38); Unsloth releases that cache while training, a variant we did not run (Appendix § 8). One FP8 copy then takes 0.940.94–1.00×1.00\times the time of FlashRL’s offloaded recipe and is 1.31.3–1.6×1.6\times as fast as bf16 training with offloading, while pinning 9 GiB of host memory instead of 32–160 GiB; in full runs of the 27B it takes 1.021.02–1.05×1.05\times the GPU-hours of the offloaded FP8 recipes, or 0.980.98–1.02×1.02\times per trained token (Table 7). The difference is in generation: FP8 weights take half the memory of bf16 weights and leave the engine a KV cache almost three times as large (Figure 9), and with long rollouts the cache sets how many sequences decode at once; FlashRL’s FP8 engine gets the same cache. Where EpiLoRA does not pay (Appendix § 8): where two copies fit, FlashRL’s faster bf16 trainer makes its recipe slightly cheaper; at 2,048 tokens a bf16 copy that keeps its KV cache ties one FP8 copy on the 9B; a trainer rounding activations like the engine trains 1.2×1.2\times faster at a larger mismatch; and the matched engine takes 1.35×1.35\times (vLLM’s Marlin, also unrounded, 1.26×1.26\times) the GPU-hours of corrected W8A8 rollouts on the H100.

9B, one L40S27B, one H100
Placement of the trainer’s bases/step ↓GiB ↓s/step ↓GiB ↓
One bf16 copy, KV keptᵃ2,2168OOM
Two FP8 copiesOOMOOM
bf16, trainer offloaded1,31448659160
FlashRL, trainer offloaded1,07641421126
EpiLoRA, FP8 trainer offloaded1,0253241894
EpiLoRA, one FP8 copy1,01294229

Table 6. With long rollouts, one FP8 copy runs close to the fastest placement that fits, with a fraction of offloading’s host memory. Seconds per RL step and peak host memory (GiB) by placement of the trainer’s base, with rollouts of up to 8,192 tokens (12-step probes; details in Table 39); FlashRL’s placement runs in our trainer and engine; the bf16 copy keeps its KV cache, which Unsloth and EpiLoRA’s FP8 copy release while training; OOM: out of GPU memory. ᵃEngine at 55% of the GPU; OOM at 70%.

PlacementGPU-h ↓ runGPU-h ↓ per M tok.Gen. s ↓Host ↓Tokens
bf16, offloaded14.670.91434160162k
FlashRL, offloaded9.840.60247127164k
EpiLoRA, FP8 offloaded9.560.6224595155k
EpiLoRA, one FP8 copy10.050.6125710165k

Table 7. In full runs, one FP8 copy trains the 27B in 0.69× bf16 offloading’s GPU-hours with a fraction of the host memory. Single 100-step RL runs (two for one copy, which releases its KV cache) with rollouts of up to 8,192 tokens on one H100; per M tok.: GPU-hours per million trained tokens; Gen.: generation seconds per step; Host: peak host memory (GiB); Tokens: trained tokens per step. These runs compare cost: at our evaluation they change accuracy little (Table 38).

GPU memory comparison for one Qwen3.8-27B RL step

Figure 9. Releasing its KV cache, one FP8 copy keeps offloading’s cache without pinning the trainer’s copy in host memory. GPU memory of one Qwen3.8-27B RL step on one 80 GB H100 with rollouts of up to 8,192 tokens, to scale: E, T and W are the engine’s, the trainer’s and a shared copy of the base (FP8 unless marked bf16), KV the engine’s cache and A the trainer’s activations; right: median host memory pinned while training.

Placement of the trainer’s baseHost, training 27B (GiB) ↓48 GB L40S 27B, 2,048 ↓48 GB L40S 9B, 8,192 ↓80 GB H100, the 27B 8,192 ↓80 GB H100, the 27B 16,384 ↓
FlashRL: FP8 engine, bf16 trainer, two copies–×c×a×a×a
FP8 engine and trainer, two copies9×cOOMOOM×a
bf16, one shared copy, KV cache kept6b×cOOMOOMOOM
bf16, trainer’s base offloaded (vLLM-style)159×c1314659OOM
FlashRL, trainer’s base offloaded125b×c1076421606d
FP8, trainer’s base offloaded934151025418673
FP8, one shared copy10OOM1167476n/r
FP8, one copy, KV cache released (EpiLoRA)84041012422701

Table 38. Of the placements we ran, only FP8 trainers fit the 27B on a 48 GB GPU, and with long rollouts one FP8 copy runs close to offloading’s speed without its host memory. Seconds per RL step (bold: fastest): the 27B’s 100-step runs on a 48 GB L40S at 2,048-token rollouts (mean over three seeds) and 12-step probes in thinking mode with rollouts of up to 8,192 or 16,384 tokens (steps 3–12). OOM: out of GPU memory (at 16,384 tokens the bf16 trainers’ runs stopped before their first or, for FlashRL’s offloaded recipe, their second step; both FP8 trainers ran all 12 steps, their mean completion length reaching 10.7–10.8k tokens). dRerun with chunked log-probs, the LM head computed in recomputed chunks so that the full logits never exist: FlashRL’s offloaded recipe then ran all 12 steps, at 73 GiB of GPU and 127 GiB of host memory; its responses trained fewer tokens per step (176k against 265–287k), so its step time is not like for like; bf16 offloading with chunked log-probs still ran out of GPU memory in its third step. All in our trainer, which keeps activation checkpoints and a micro-batch’s logits on the GPU. Host, training: the 27B run’s median host memory over its steps 2–4 on the H100 (Table 6 gives each placement’s peak). One bf16 copy runs out of memory at engine share 0.70 as shown; at 0.55 it fits the 9B at 8,192 tokens, at 2,216 s per step (Table 6). FlashRL: vLLM’s FP8 engine beside a bf16 trainer, with TIS. Unsloth’s FP8 standby shares one FP8 copy as the last row does, with a trainer that rounds activations alike; its bf16 standby needs at least bf16 offloading’s GPU memory while it trains. aNot run: it needs more GPU memory than a placement that ran out of it. bFrom the probe’s own trace. cNot run: its copies of the base alone exceed the GPU’s memory. n/r: not run. Over full 100-step runs at 8,192 tokens on an H100 (Table 7) the final accuracies (OlympiadBench and Minerva, non-thinking, 2,048 tokens) are: one FP8 copy 50.6 and 52.1 (two seeds), FlashRL’s offloaded recipe 51.0, FP8 offloading 51.6, bf16 offloading 51.1, against 51.1 for the 27B before RL: these runs change accuracy little at this evaluation, so they compare cost, not learning.

Related Work

Multi-adapter serving. Other systems accelerate per-request and per-token adapters ([61, 20]), tile adapters of mixed ranks ((Mi et al., 2025)), multiply each group of rows by its own matrix ((Ye et al., 2025), or cache, pool, compress and co-quantize adapters or share KV caches across them ([64, 18, 66, 68, 1, 59] (Yu et al. 2026b)). All of them compute adapters beside the base GEMM (Table 9); EpiLoRA can be their kernel layer.

Serving systemMany tenantsFusedLow-precision adapters
Punica, S-LoRA✓××
vLLM, SGLang✓××
TensorRT-LLM✓×✓
SVDQuant×✓×
LoRAFusion2✓✓×
FlexLLM1✓××
EpiLoRA✓✓✓
RL systemLow-precision rolloutsOne copyMatched
FlashRL✓××
Jet-RL3✓××
QeRL✓×✓
Unsloth4✓✓✓
Unsloth, FP83✓✓×
EpiLoRA✓✓✓

Table 9. EpiLoRA brings many tenants’ adapters and an RL trainer onto one low-precision base. Capabilities of the closest systems, not their speed. Fused: adapters computed inside the low-precision base GEMM. One copy: the trainer computes on the engine’s copy of the base. Matched: engine and trainer compute on the same weights with activations unrounded, so RL needs no off-policy correction.

Low-precision serving. AWQ and QServe quantize weights to 4 bits with 16- or 8-bit activations ([27, 28]), Marlin provides fast mixed-precision GEMMs ([7]), and Hopper and Blackwell GPUs support FP8 and block-scaled 4-bit formats natively ([37, 48, 40]); EpiLoRA keeps each engine’s quantization bit for bit and changes only how adapters are computed around the GEMM.

Fusing low-rank branches into GEMMs. SVDQuant’s Nunchaku fuses one low-rank branch, shared by every token, into the 4-bit GEMMs of diffusion models ([23]). For fine-tuning, LoRAFusion fuses the up-projection into the bf16 base GEMM and routes the tiles of a mixed-adapter batch to their adapters ([75]). K2 does the same for serving over FP8 and NVFP4 GEMMs, gathering rows sorted by adapter as mixture-of-experts GEMMs gather rows sorted by expert ([9]). The decode path applies a known principle, overlapping a producer with its consumer’s main loop, as systems that overlap a GEMM with communication between GPUs do tile by tile ([58, 42, 2]) and NanoFlow does across a batch’s operations ([74]). Unlike weight prefetches issued before a dependent kernel’s wait, our producer is a side branch that needs only in its epilogue, so its whole main loop overlaps it; LoRAFusion needs the down-projection before its GEMM starts, and two-stream modes add the adapter’s output after the GEMM ends. Compressing adapters ([43, 29, 39] (Yadav et al. 2023)) and training them for low-precision merges ([6, 11] (Li et al. 2024b; Xu et al. 2024; Li et al. 2026)) are complementary: they change the adapters.

Low-precision RL. FlashRL, our RL baseline, pairs FP8 rollouts with truncated importance sampling beside a bf16 trainer ([30]). Quantized rollouts also speed up full-weight RL, which must re-quantize the weights at every step (Hu et al., 2026; Miah & Belilovsky, 2026); other systems train and generate at one low precision, rounding activations on both sides, which leaves a mismatch unless both sides’ kernels agree bit for bit ([44, 3] (Xi et al. 2026; Gu et al. 2026)), correct the rounding of 4-bit rollouts ([76, 60]), or sample from 4-bit weight-only kernels ([17]). Adaptive importance sampling tunes the correction ([73]) and score centering removes the drift a mismatched sampler adds to the policy gradient ([32]); the matched engine instead removes most of the mismatch itself. FlexLLM co-serves inference and fine-tuning on one bf16 base ([36]), batch-invariant kernels make engine and trainer agree bit for bit (He & Thinking Machines Lab, 2025; vLLM Team, 2025), Unsloth shares one 4-bit or FP8 copy of the base between engine and trainer and releases its KV cache while training (Unsloth AI, 2025b;c), and a TRL patch shares FP8 weights with vLLM (ingambe, 2026). QLoRA first trained adapters on a frozen quantized base ([6]). EpiLoRA trains on the engine’s FP8 codes with bf16 activations, and its matched engine keeps them unrounded, as Marlin and QeRL do, in the trainer’s layout.

Conclusion

Adapters have stayed outside the low-precision GEMM, so a cheaper base makes them relatively costlier to serve. EpiLoRA, a drop-in vLLM plugin, brings them back into the GEMM: on Ada GPUs it fuses every tenant’s adapter into its own low-precision GEMM, and on Hopper and Blackwell GPUs it launches the down-projection first and lets the GEMM stream its weights while the adapter is computed, waiting for it only in the epilogue. This change makes one adapter at batch 1 faster than vLLM, SGLang and TensorRT-LLM on Hopper GPUs, and with many tenants its adapter kernels keep more of the base’s throughput than vLLM’s on most GPU-model pairs we tested. The same low-precision weights also serve RL: a trainer that computes on them needs no bf16 copy of the model, so a 27B model trains on one 48 GB GPU, and an engine that leaves activations unrounded needs no importance correction. Served adapters keep the accuracy of vLLM’s LoRA path, and on the 9B, corrected or matched FP8 RL ends where bf16 training does. Sharing one FP8 copy between engine and trainer saves most of a second copy’s memory, which matters most for models that do not fit twice on a GPU; with long rollouts it nears the fastest placement with a fraction of its host memory. More broadly, a kernel can wait for its producer where it first needs the data, not where it starts.

These appendices hold the measurements behind the paper’s claims. Appendix A discusses where EpiLoRA helps and its limitations, and Appendix B what each part of the RL system buys; Appendices C to F give the closest systems and the kernel, serving and RL measurements; Appendix G gives the code and the software.

Discussion and Limitations

Where a single low-precision base helps. EpiLoRA helps where the adapter’s cost or the base’s memory is the bottleneck. At small decode batches, vLLM’s LoRA kernels add 35–55% to a Hopper layer, and the decode path removes most of it. With many tenants, EpiLoRA’s adapter kernels keep more of the base’s throughput, and adapters stored in low precision let more of them stay resident. In RL, an FP8 trainer removes the bf16 copy of the base, which lets larger models train on smaller GPUs. Other engines could adopt the decode path by adding the up-projection in a CUTLASS epilogue that waits for a down-projection launched first. Computing the down-projection inside the normalization kernel that already reads the input would hide it for the query/key/value and gate/up projections, whose inputs come from a normalization, but not for the output and down projections. Its gain is bounded by what stays exposed today: at one decode token, the exposed down-projection and the second kernel’s launch together take 1.4–2.2 µs of the 10–12 µs a rank-64 adapter still adds to a Hopper layer Table 13; the rest is the epilogue’s extra work, so a leaner epilogue is the next step.

GPUTokensGEMM vs. CUTLASSEpilogueExposed + launchAdapter total
H1001−5.010.22.212.5
H1004−5.99.61.811.4
H1008−5.99.01.810.8
H10016−2.37.73.110.8
H2001−1.98.81.410.2
H2004−3.37.61.69.1
H2008−2.47.81.59.3
H20016+0.16.72.59.2

Table 13. Under the decode path, the epilogue, not the down-projection, is most of the adapter’s remaining cost. One Qwen3-8B decoder layer (its four projections) with one rank-64 adapter at 1–16 decode tokens; CUDA graphs, L2 cache flushed. GEMM vs. CUTLASS: EpiLoRA’s GEMM without an adapter minus vLLM’s quantization and CUTLASS GEMM; epilogue: the GEMM with the adapter, the down-projection’s result given, minus the GEMM without it; exposed + launch: the decode path minus the former; adapter total: the decode path over EpiLoRA’s own GEMM. Microseconds per layer ↓

Where it does not. Where neither memory nor latency is the bottleneck, simpler designs are as good or better. Where two copies fit, FlashRL’s recipe is the cheapest: on the 9B at 2,048-token rollouts on an H100 it takes 2.96 GPU-hours per run, against 3.10 for our FP8 trainer with TIS (the difference is in training) and 3.22 for bf16 training. At 2,048 tokens one copy saves memory, not time, on the 9B: one bf16 copy that keeps its KV cache trains as fast as one FP8 copy on the L40S (174 against 173 s per step), while on the 27B the FP8 copy is 1.33× faster Table 37. With rollouts of up to 8,192 tokens, the bf16 copy that keeps its KV cache takes 2.2× EpiLoRA’s step time on the 9B, or 1.9× that of an FP8 copy that also keeps it Table 38; Unsloth’s bf16 mode, which releases the cache while training and which we did not run, holds bf16 offloading’s memory without its weight transfers, so it would take at most bf16 offloading’s 1.3×. The matched engine samples more slowly than a W8A8 engine: it takes 1.35× the GPU-hours of corrected W8A8 rollouts on the H100, almost all in generation, and 0.95–1.03× on the L40S Table 35 and Table 36. vLLM’s Marlin engine, which also keeps activations in 16 bits, reaches the same accuracy at 3.9 GPU-hours, so the matched engine’s advantage, a layout the trainer can share, is unproven; on the L40S one copy ended 3.4 points below bf16 training ([−5.7, −1.1]). With long rollouts on the 27B, FlashRL’s steps trained 19% more tokens than ours in the 12-step probes Table 39, and in the full 100-step runs its recipe needed 2–3% fewer GPU-hours per trained token than ours Table 7, since its bf16 trainer is faster; at 16,384 tokens the bf16 trainers run out of memory, and FlashRL’s recipe fits only with chunked log-probabilities and then takes 0.86× our step time while training fewer tokens per step Table 38. A trainer that rounds its activations as the engine does (our proxy for Unsloth’s FP8 trainer; S8RA in Table 30) trains 1.2× as fast as ours on the L40S (35–39 against 45–46 s of training per step, from the runs’ logs), at 1.4–1.6× our trainer’s mismatch with the engine. With 32 tenants, TensorRT-LLM delivers 1.05× EpiLoRA’s throughput on the H200 in its own job, SGLang 0.5.9 delivers 1,803 tokens/s on Qwen3-8B on the L40S against EpiLoRA’s 1,761 in Table 2’s job (1.02×, in separate jobs; SGLang’s engine without LoRA delivers 3,179), and vLLM’s kernels tuned for Llama-3.1-8B match ours.

ConfigurationRunsGeneration ↓Training ↓Total ↓Generation share
bf16 engine and trainer92.001.223.2262%
FlashRL’s recipe91.771.192.9660%
W8A8 with TIS, FP8 trainer91.741.363.1056%
matched engine, FP8 trainer92.811.394.2067%

Table 35. On the H100, the matched engine’s extra GPU time is almost all in generation. GPU-hours per 200-step run of the nine-seed H100 arms of Figure 13, split into generation and the rest of each step (training and the adapter’s sync), from the runs’ step logs.

Table 37. With 2,048-token rollouts, one bf16 copy that keeps its KV cache trains as fast as one FP8 copy on the 9B; on the 27B the FP8 copy is 1.33×1.33 \times faster. 12 steps of the RL recipe with one bf16 copy shared by engine and trainer that keeps its KV cache while training (Unsloth releases it) against the same steps of full runs with one FP8 copy and with bf16 offloading; seconds per step over steps 3–12 and the run’s peak host memory.

Model, GPUCopyShareKV (GiB) ↑Rollout ↓All ↓Host peak (GiB) ↓
Qwen3.5-9B, L40Sone bf160.555.51872458.6
Qwen3.5-9B, L40Sone bf160.7012.212017410.1
Qwen3.8-27B, H100one bf160.809.113616514.2
Qwen3.5-9B, L40Sone FP80.5510.31221737.4
Qwen3.5-9B, L40Sbf16, offloaded0.8015.312718248.2
Qwen3.8-27B, H100one FP80.6519.59412410.6
Qwen3.8-27B, H100bf16, offloaded0.8512.6129161159.5
Placements/step ↓generation (s) ↓response (tokens)trained tokensKV (GiB) ↑
9B, one L40S
One bf16 copy, KV kepta2,2161,8867,992527k4.2
bf16, trainer offloaded1,3149637,902558k15.3
FlashRL, trainer offloaded1,0767327,906549k21.4
EpiLoRA, FP8 trainer offloaded1,0257257,888544k21.4
EpiLoRA, one FP8 copy1,0127227,795531k21.4
27B, one H100
bf16, trainer offloaded6595215,575244k12.6
FlashRL, trainer offloaded4212865,549248k34.8
EpiLoRA, FP8 trainer offloaded4182915,549206k34.8
EpiLoRA, one FP8 copy4222905,545207k34.8

Table 39. The long-rollout probes behind Table 6. Per placement: seconds per step and generation seconds per step (steps 3–12; a steps 3–11, ending at 55% of the GPU), mean response length, trained tokens per step (responses in groups with zero reward variance are not trained) and the engine’s KV cache. Bold: best within each model, and any within 2% of it.

Limitations.

  1. Hardware. Datacenter Blackwell GPUs (sm_100) are untested; our Blackwell results come from a MIG slice of an RTX PRO 6000 (sm_120). sm_100’s shorter main loops would leave more of the down-projection exposed. The first-wave window used in Figure 5 is an empirical fit chosen after the data, and it overestimates the exposure beyond two waves. Our Triton GEMM trails CUTLASS on the H100 above 32 tokens, and at one token by up to 28% on narrow tensor-parallel shards and on small models Table 16.

ShapesMMnnK2 / CUTLASS ↓\downarrow
minmedianmax
Qwen3-8B1–16160.930.961.00
Qwen3-1.7B180.921.081.12
Qwen3-0.6B181.151.161.24
Qwen3-8B, TP2140.981.151.28
Qwen3-8B, TP4140.971.061.23
Qwen3-8B, TP8140.991.191.22
Qwen2.5-72B, TP21–32200.930.991.07

Table 16. Table 16. Without LoRA, the decode path’s GEMM matches CUTLASS on the 8B’s and the 72B’s shapes, and trails it on small and narrow ones. K2 without an adapter (with its own per-token quantization) over vLLM’s no-adapter path (per-token quantization, then CUTLASS) per projection on the H100 (MM tokens, nn cases; CUDA graphs; CUTLASS with vLLM’s quantization), from the kernel benchmark’s rows (Table 10 and Table 17; the 72B’s shards from their own run, whose tiles the job of Table 28 uses); K2’s tiles fully searched except for Qwen3-8B’s tensor-parallel shards (a coarse sweep). Where K2 is slower, its longer main loop also widens the window that hides the shrink.

  1. Scope of serving. The tensor-parallel path is opt-in and was tested only at two-way parallelism on one 72B model, where its bf16-head requires tiles searched for the shards and where it ties vLLM with 8 tenants. Mixture-of-experts LoRA and block-scaled FP8 bases are untested, and speculative decoding was tested only with n-gram drafts on one GPU, where the decode path serves verification steps in 0.81–0.82× vLLM’s time per token except in runs whose drafts stopped matching after a first token that run-to-run nondeterminism flipped, as in some of vLLM’s own runs. TensorRT-LLM ran on one model, with its PyTorch backend and a single-process worker.

  1. Workloads. Serving speed uses random adapters and, except for one WildChat trace, synthetic requests; storage accuracy uses only RL-trained adapters; and every model except Llama-3.1-8B is a Qwen model.

  1. Default CUDA-graph sizes. At vLLM’s default capture sizes, EpiLoRA’s operators run eagerly for prefill steps beyond 512 tokens. It still leads online at every rate on the H200, on the 72B with tensor parallelism and on the

H100 with long prompts, but with 512-token prompts it trails vLLM on the H100 at 16 requests/s (1.18×1.18\times its time per output token, p99 TTFT 1.111.11–1.20×1.20\times) and trails it slightly on the L40S at 4–6 requests/s (1.02×1.02\times, p99 TTFT up to 1.32×1.32\times; Table 24). The kernel-level account (Table 27): the timed dispatch, which times its candidates on CUDA-graph replays, mixes vLLM’s adapter kernels into some projections and so pays both systems’ launch costs on the H100’s host CPU, and EpiLoRA’s own down-projection took twice vLLM’s GPU time at these batch sizes. Handing every batch above 32 tokens to vLLM’s kernels widens the gap (1.23×1.23\times and 1.26×1.26\times at 8 and 16 requests/s). Two opt-in settings chosen after these diagnostics, EpiLoRA’s own kernels above the capture bound and tiles searched for large batches, narrow it to 1.07×1.07\times at 16 requests/s with p99 TTFT within 1.1×1.1\times. Timing the dispatch’s candidates as they run outside the graphs, host time included, would let it choose these settings where they help.

H100 vLLMH100 EpiLoRAH100 own kernelsH100 own, new tilesH200 vLLMH200 EpiLoRAH200 own kernelsH200 own, new tiles
Step time (ms)66.771.071.767.465.561.468.664.5
Host time, profiled (ms)75.388.274.371.965.759.266.262.8
GPU time (ms)65.660.667.362.968.059.066.561.1
CUTLASS FP8 GEMM22.622.722.722.722.422.322.422.4
down-projection (shrink)5.05.211.56.85.66.813.07.3
up-projection (expand)20.917.517.818.122.716.017.217.4
activation quantization and norms11.59.69.89.911.98.68.78.8
everything else5.65.75.55.55.35.35.25.2

Table 27. Where the time of a step outside CUDA graphs goes. One four-tenant step of Qwen3.5-9B that prefills 2,048 tokens (FP8 base, 32 rank-64 adapter slots), above vLLM’s default CUDA-graph sizes, so it runs outside the graphs; median step time over 20 steps, and the host (CPU) time and GPU time of one profiled step by kernel class, milliseconds. EpiLoRA: its default configuration, whose timed dispatch may pick vLLM’s adapter kernels inside its operator; own kernels: EpiLoRA restricted to its own adapter kernels above 512 tokens; new tiles: the same with the down-projection’s tiles searched for large batches.

  • (v) Co-serving. Serving tenants from the copy that RL trains on works but does not pay off: on one L40S it saves only 5–8% of the GPU-hours of two separate deployments (0.920.92–0.95×0.95\times), RL’s steps take 1.75×1.75\times as long, and the tenants’ p99 time to first token rises from 0.26 to 2.87 s.

  • (vi) Batch-dependent numerics. A request’s tokens are computed by the decode path in decode batches of up to 32 tokens and by the vendor GEMM with adapter kernels above, so greedy outputs can change with the batch’s composition, as they do between vLLM’s own kernel configurations: on 16 prompts, every alternative configuration, vLLM’s tuned and dual-stream kernels included, diverges from vLLM’s default path on 5–9 in the output check of our H100 end-to-end test, where EpiLoRA’s decode path diverges on at most one more; without adapters, the two engines’ outputs are identical. Deployments that need batch-invariant outputs need batch-invariant kernels ([13]).

What Each Part of the RL System Buys

Table 8 summarizes, for each part of the RL system of § 3.3, what it buys and what it costs, measured on Qwen3.5-9B on one L40S unless noted; Table 36 gives the full per-configuration accounting. Running the matched engine, one copy and the KV release together in a 30-step probe takes 153 s per step, against 155 s with the KV cache kept, with twice the KV cache (23 against 12 GiB).

PartBenefitCost
FP8 trainer weights6.5 GiB less GPU memory for the 9B and 22 GiB for the 27B; the 27B trains on one 48 GB L40SNone measurable: 7.2 GPU-hours, the same as FlashRL’s bf16 trainer
One shared copyPinned host memory falls from 48–159 GiB to 7–11 GiBNone measurable: 7.1 against 7.2 GPU-hours with offloading
KV-cache releaseA 21 GiB instead of a 10 GiB KV cache during rolloutsNone measurable at 2,048 tokens; at 8,192 tokens the 27B’s KV-releasing copy took 1.05× FP8 offloading’s GPU-hours
Unrounded (W8A16) rollouts, ours or Marlin’sNo correction needed: uncorrected runs end where W8A8 with TIS doesOn the H100, 4.2 GPU-hours (Marlin 3.9) against 3.1 for W8A8 with TIS; on the L40S, 8.8 against 9.3 resident and 7.2 offloaded; with one copy at engine share 0.55, −3.4 points [−5.7, −1.1] against bf16

Table 8. What each part of the RL system buys and costs. Qwen3.5-9B on one L40S unless noted; GPU-hours per 200-step run; accuracy differences with 95% confidence intervals.

The closest systems

Table 9 compares EpiLoRA’s capabilities with those of the closest serving and RL systems. Services that train and serve many users’ adapters on shared bases (Tinker, MinT, MARLaaS) do not document their base precision or kernels, so they are not in the table.

Kernel details

One output tile of K2 (Figure 10). K2 runs the low-precision GEMM over tokens sorted by adapter, so each output tile meets a few contiguous groups of rows, and its epilogue adds each group’s up-projection.

One output tile of K2

Figure 10. One output tile of K2. Rows are tokens sorted by adapter, forming contiguous groups (here three). After the low-precision main loop, the epilogue adds each group’s down-projection hh multiplied by its adapter’s slice of BB, then writes the rows back in token order.

Table 1’s L40S column comes from the weight-only kernel benchmark: Marlin’s W8A16 GEMM with vLLM’s tuned LoRA kernels against EpiLoRA’s fused weight-only kernel, one Qwen3-8B layer at one decode token with one rank-64 adapter, CUDA graphs with the L2 cache flushed; its other columns come from the rows of Table 10.

Table 10. On the H100, the H200 and the Blackwell slice, the decode path brings one adapter’s cost to a few percent of the layer. One Qwen3-8B decoder layer (the four projections summed; FP8 base on the Hopper GPUs, our NVFP4 checkpoint on the Blackwell slice), one rank-64 adapter, CUDA graphs with the L2 cache flushed, microseconds (lower is better); each path at its best tile configuration. Previous: the plugin’s best path before the decode path (Hopper: hybrid or swap-AB; Blackwell: fused or hybrid). Bold: the fastest path with the adapter. Last column: the PDL path’s time over CUTLASS alone.

µs ↓vLLM’s LoRA pathOursPDL over
TokensCUTLASS alonedefaulttuneddual-streampreviousno PDLPDLCUTLASS (%)
H100, FP8 base
1126205179180188163134+6
4128195179172192162133+4
8128195179172191157133+4
16128196180174181167136+7
H200, FP8 base
1104190161167171144112+8
4106180162157174147112+6
8106180162156175144113+7
16106180162159164150115+9
Blackwell slice, NVFP4 base
1286388342340316330293+3
4285360339325317339298+5
8288359343326320342298+3
16291359348334324345312+7

Table 10.

The decode path across decode batch sizes (Table 10). From one to sixteen tokens, one rank-64 adapter adds 4–9% to a Qwen3-8B layer on Hopper GPUs and 3–7% on the Blackwell slice with the decode path, against 34–61% and 13–20% for vLLM’s tuned or dual-stream kernels.

Controls for the overlap (Table 11 and Table 12). Serializing a library-fast down-projection (cuBLAS’s GEMM or GEMV) before the same GEMM leaves the layer 1.17–1.25× slower than the decode path, and our own down-projection at its fastest split-K 1.13–1.17×; only a serialized kernel that does no work comes within 2%.

Where the adapter’s remaining cost goes (Table 13). Under the decode path, most of what the adapter still adds to a layer is the epilogue’s extra work; the exposed down-projection and its launch take 1–3 μs per layer, and EpiLoRA’s GEMM without an adapter is up to 6 μs faster than CUTLASS with vLLM’s separate quantization.

Which path the dispatch picks (Table 14). At engine start, the plugin times every candidate path per projection shape and batch-size bucket, on CUDA-graph replays with one adapter, and keeps the fastest. On the H100 with 32 rank-64 adapter slots it picks the decode path for every projection up to 32 tokens; above, it picks EpiLoRA’s adapter kernels after the vendor GEMM for the projections that combine three or four outputs, where the projection, vendor GEMM included, takes 0.53–0.85× its time with vLLM’s kernels, and vLLM’s own adapter kernels for the others.

**Why EpiLoRA’s adapter kernels win on these projec-

Table 12. Our own down-projection at its fastest split-K, run before the same GEMM without overlap, still trails the decode path. Table 11’s cases: one Qwen3-8B layer’s four projections at one decode token with one adapter, the down-projection at every split-K from 1 to 32, serialized before the GEMM; per projection the fastest split, microseconds summed over the projections (CUDA graphs). Ratio: serialized over the decode path, for the layer and per projection. Shrink alone: the down-projection at the decode path’s split and at the fastest.

[tions](Table 15). In the profiled step of Table 27, EpiLoRA’s GPU time on the H100 is 5.0 ms below vLLM’s: 3.4 ms in the up-projection (expand) and 1.9 ms in activation quantization and norms, which EpiLoRA’s base path computes with vLLM’s CUDA quantization kernel rather than the kernels vLLM’s compiler generates (as much with EpiLoRA’s own adapter kernels throughout, so not from them), while its down-projection takes 0.2 ms more. We attribute the up-projection’s difference to how the kernels index a projection’s outputs. vLLM’s expand kernel treats them as slices and launches one program per adapter, slice and tile of the widest slice, so the programs of narrower slices past their width exit without work. With several slices it reads each slice’s starting column in the output from memory at run time, and when their widths differ, as for attention’s query, key and value under grouped-query attention (8,192, 1,024 and 1,024 columns in Qwen3.5-9B) and for the linear-attention input (2,048, 2,048, 4,096 and 4,096), also each slice’s strides, which the compiler then cannot specialize. EpiLoRA’s expand-add tiles the concatenated output instead, each tile inside one slice, and receives the slice boundaries as kernel arguments. The costs per output column are consistent with this account: with tiles tuned for these shapes, vLLM’s expand takes 7.9 ns on the projections with one output, 13.4 ns on gate/up’s two equal ones and 21.6 ns on the three or four unequal ones, while EpiLoRA’s takes 12.1–13.1 ns on every kind, so on a single output vLLM’s one kernel is the faster and the dispatch keeps it there. On the H200, vLLM’s expand takes 17.4 ns per output column on the projections with two to four outputs against EpiLoRA’s 11.7, and 8.8 against 13.1 ns on those with one. We did not time vLLM’s kernel launched once per slice or with the slice offsets as constants, either of which might close the gap, and these costs come from one model’s 2,048-token step outside CUDA graphs; at the 64–512-token batches of the many-tenant runs, Table 14 shows the same ordering by kind of projection. The gain also depends on the model: on Llama-3.1-8B, whose query, key and value have Qwen3-8B’s widths, vLLM’s tuned kernels tie EpiLoRA with 32 tenants (Table 2). EpiLoRA’s kernels read vLLM’s own adapter buffers, so vLLM could adopt them.

When K2 is slower than CUTLASS (Table 16). Without an adapter, K2 matches CUTLASS on the 8B’s and the 72B’s shapes and trails it on small and narrow ones, which lengthens its main loop and so widens the window that hides the down-projection.

The exposure model (Table 17). On every measured projection, the exposed time against the down-projection’s time and the GEMM’s main loop. The window of the GEMM’s first wave of thread blocks, assuming one block per SM, was fitted after the data; with the resident blocks per SM that the CUDA occupancy API reports (two in most Hopper cases), the same window tracks the exposure less well.

GPUShapesCasestshrinkt_{\mathrm{shrink}} (μ\mus)tmaint_{\mathrm{main}} (μ\mus)medianmaxwhole loopfirst wave
H100rank 642410.4–18.316.7–50.50.61.30.60.6
H100rank 16811.8–15.518.7–50.82.66.12.60.7
H100rank 128814.0–18.518.7–50.85.09.35.02.3
H100TP 2 shards410.0–14.515.0–33.33.75.13.72.1
H100TP 4 shards410.7–14.214.4–20.62.54.32.51.1
H100TP 8 shards410.4–14.312.6–19.33.15.42.51.9
H200rank 64169.2–17.814.9–41.50.41.20.40.4
H200rank 1688.4–14.715.3–41.40.21.00.20.2
H200rank 12889.8–18.215.3–43.01.55.71.51.5
H200TP 2 shards49.5–13.714.2–28.23.24.83.21.7
H200TP 4 shards49.9–13.713.1–17.61.54.81.51.1
H200TP 8 shards49.7–13.511.7–17.43.25.72.71.9
GH200TP 8 shards39.4–13.811.4–17.04.34.92.52.5
GH200Qwen3-0.6B, -1.7B167.8–11.512.3–18.00.00.50.10.1
Blackwell slicerank 641615.0–29.731.6–126.00.72.10.90.9

Table 17. What stays exposed of the down-projection, on every measured projection (Figure 5). Per GPU and shape family, with one adapter at 1–16 decode tokens (tensor-parallel shards: Qwen3-8B’s per-GPU shapes at one token): the down-projection alone (tshrinkt_{\mathrm{shrink}}), the GEMM’s main loop (tmaint_{\mathrm{main}}), the exposed time (the decode path’s time minus the same GEMM with the down-projection already computed), and the median error of the bound max⁡(0,tshrink−twindow)\max(0, t_{\mathrm{shrink}} - t_{\mathrm{window}}) with the whole main loop as the window and with the main loop of the GEMM’s first wave of thread blocks, assuming one block per SM (a window fitted after the data). Where the CUDA occupancy API reports two resident blocks per SM, as it does in most Hopper cases, the first-wave window tracks the exposure less well (Pearson 0.50 against 0.71 for the one-block window on the same 115 projections; 0.70 over all projections, as in Figure 5).

Many distinct adapters (Figure 11). With one adapter in a 32-token batch, EpiLoRA adds 11% to the H100 layer where vLLM’s tuned kernels add 38%; with 32 distinct adapters the adapters’ own weights dominate the step, and storing them in FP8 or NVFP4 nearly halves the overhead on the L40S.

End-to-end serving

Batch 1 against throughput (Figure 12). Every vLLM configuration and every EpiLoRA path on each GPU, one point per configuration.

SGLang and TensorRT-LLM (Table 18 to Table 20). Each system ran in its own job beside EpiLoRA, which gives the same-job ratios of Table 3; Table 21 gives the GPU profiles behind its Nsight row.

H100H200
TRT-LLMEpiLoRATRT-LLMEpiLoRA
Time per token, no LoRA (ms) ↓4.59ᵇ4.524.08ᵇ4.11
Time per token, one adapter (ms) ↓6.93ᵃ4.856.89ᵃ4.53
Throughput, one adapter (tok/s) ↑6,858ᵇ7,7037,256ᵇ7,654
Throughput, 32 adapters (tok/s) ↑5,901ᶜ6,072ᶜ6,269ᵇ5,985

Table 20. EpiLoRA against TensorRT-LLM, each pair measured in one job. Qwen3-8B with an FP8 base and rank-64 adapters, CUDA graphs; for each metric, TensorRT-LLM’s fastest configuration we found: ᵃits block-FP8 checkpoint with the LoRA path overlapped with the base; ᵇthe ModelOpt FP8 checkpoint, LoRA overlapped; ᶜthe same at a KV-cache share of 0.6, found after the first run, with EpiLoRA measured again in that job. Without LoRA, EpiLoRA is vLLM’s engine.

SystemTime per token (ms) ↓Added GPU time (ms) ↓
no adapterone adapter
vLLM, two streams4.396.521.87
SGLang5.108.233.01
TensorRT-LLM4.607.502.87
EpiLoRA4.394.920.40

Table 21. Where the batch-1 time goes. Time per output token for Qwen3-8B with an FP8 base on one H100, without and with one rank-64 adapter, and the GPU time the adapter adds per token, measured with NVIDIA’s Nsight Systems profiler. Each system uses CUDA graphs; vLLM runs its dual-stream mode with its default LoRA kernels (6.02 ms with tuned ones, Table 2) and TensorRT-LLM its overlapped mode.

Served adapters’ accuracy (Table 22). The test behind Figure 8.

Accuracy (%) ↑OneDifference
BenchmarkvLLMEpiLoRAonly(95%)pp
bf16-trained adapter
MATH-50091.490.414/9−1.0 ± 1.90.40
Olympiad61.863.436/47+1.6 ± 2.60.27
Minerva54.454.47/7+0.0 ± 2.71.00
W8A8-trained adapter
MATH-50089.090.213/19+1.2 ± 2.20.38
Olympiad58.458.540/41+0.1 ± 2.61.00
Minerva55.555.98/9+0.4 ± 3.01.00

Table 22. EpiLoRA serves adapters at the accuracy of vLLM’s LoRA path. Two RL adapters of Qwen3.5-9B on an FP8 base, greedy decoding, on MATH-500 (500 problems), OlympiadBench (675) and Minerva (272): accuracy with each system, the problems only one of them solves (vLLM/EpiLoRA), the difference (EpiLoRA’s accuracy minus vLLM’s, in points) ± the half-width of its paired 95% interval, and McNemar’s exact pp.

With CUDA graphs over the prefill sizes (Table 23). Every point of Figure 6’s solid series, and the same test with rank-16 tenants.

GPURateMedian TPOT (ms) ↓p99 TTFT (ms) ↓
vLLMEpiLoRARatiovLLMEpiLoRARatio
H10029.97.90.792132541.20
415.513.00.842382741.15
828.929.01.002683051.14
1654.864.71.183604001.11
H100, new tiles29.97.70.782142421.13
415.512.50.812432621.08
829.027.70.952692891.08
1654.961.11.113613861.07
H100, new tiles, own kernels29.97.50.762142131.00
415.511.80.762432400.99
829.024.90.862692660.99
1654.958.61.073613861.07
H20028.86.50.74961111.17
412.08.90.741201381.14
821.217.60.831661640.99
1646.643.10.932702430.90
L40S123.721.90.932352411.03
232.031.10.972552621.03
451.452.21.023323391.02
686.988.51.023,0624,0541.32

Table 23. Online serving with CUDA graphs captured up to 2,048 tokens for both systems. Qwen3.5-9B with an FP8 base, 32 tenants, Poisson arrivals at the given rates, 512-token prompts and 256 output tokens; EpiLoRA’s median time per output token and p99 time to first token over those of vLLM with tuned LoRA kernels, means of three server starts. On the H100 at 4 requests/s, one of EpiLoRA’s three starts has an outlying p99 TTFT (335 against 150–160 ms); at 6 requests/s the L40S is past saturation, where vLLM’s p99 TTFT grows to seconds.

The WildChat trace (Table 5). Of 4,000 first turns, vLLM’s benchmark keeps the 3,383 with at most 1,024 prompt and 2,048 total tokens: prompts have a median of 23 tokens (90th percentile 247), replies 253 (759), and each request generates its recorded reply length. Each start draws 1,000 of them, a different sample per start and the same for both systems, with 85–102 prompt and 334–345 reply tokens on average. vLLM’s p99 TTFT of 1.37 s on the H100 at 8 requests/s comes from one of its three starts (3.7 s; the other two 0.19–0.20 s); EpiLoRA’s p99 TTFT there is 1.06–1.13× vLLM’s at the other three rates.

At vLLM’s default CUDA-graph sizes (Table 24). Steps that prefill more than 512 tokens run outside CUDA graphs; with 512-token prompts and 32 tenants, EpiLoRA lowers the time per output token at every rate on the H200 and at low rates on the H100 and the L40S.

Long prompts, a 72B model and host time (Table 25 and Table 26). At vLLM’s default CUDA-graph sizes, with direct launches, EpiLoRA lowers the time per output token with 2,048-token prompts on both Hopper GPUs and on a 72B model; Table 26 gives each path’s host and GPU time per layer for the steps that run outside CUDA graphs.

Path, μs\mu\mathrm{s} ↓H100 512 tokens hostH100 512 tokens GPUH100 2,048 hostH100 2,048 GPUH200 512 tokens hostH200 512 tokens GPUH200 2,048 hostH200 2,048 GPUL40S 512 tokens hostL40S 512 tokens GPUL40S 2,048 hostL40S 2,048 GPU
vLLM’s quantize, GEMM and LoRA kernels6489166392211299982287233246015794544320
the vendor GEMM alone13620813873268215697301017131002952
ours, hybrid5418705321769260875258175640516894024211
ours, hybrid, direct launches2458732431768125876125175618016861814354
ours, fused350169934539211741804171416426515542593490
ours, fused, direct launches10617001063919591802594142801559813474

Table 26. Host and GPU time per layer for the steps that run outside CUDA graphs. One layer of Qwen3.5-9B (its four projections) with 32 adapter slots at the batch sizes of vLLM’s eager steps, microseconds: the host time to launch each path (calls queued behind a GPU sleep, so the GPU never throttles them) and its GPU time (CUDA-graph replays). The H100’s host CPU (Intel Xeon Gold 6448Y) pays about twice the host time per call of the H200’s (AMD EPYC 9335). Direct launches halve EpiLoRA’s host time and give bit-identical outputs.

Where a step outside CUDA graphs spends its time (Table 27). In a step that prefills 2,048 tokens, EpiLoRA’s default configuration takes less GPU time than vLLM but more host time, because its timed dispatch picks vLLM’s adapter kernels for some projections and pays both systems’ launch costs, which the H100’s host CPU makes larger.

Restricted to its own kernels, EpiLoRA matches vLLM’s host time but its down-projection takes about twice vLLM’s GPU time at this batch size. Tiles searched for large batches remove most of that difference, bringing the step to 1.01× vLLM’s time on the H100 and 0.98× on the H200. Online on the H100, the two settings together narrow the gap at 16 requests/s from 1.18× to 1.07× and bring the p99 time to first token within 1.1× at every rate (Table 24). On the H200, whose host CPU is faster, EpiLoRA’s default dispatch, which mixes in vLLM’s kernels, is faster still (0.94× vLLM’s step time), so the settings stay opt-in.

The 72B model (Table 28 and Table 29). Across two H100s with tensor parallelism, against vLLM’s LoRA kernels tuned for the shard shapes, and on one H200 with the timed dispatch and with the decode path forced on every projection.

PathW1 (ms) ↓over no LoRA ↓W2 (tok/s) ↑
vLLM, tuned20.40 [20.31, 20.54]+24%2,141
vLLM, dual, tuned20.31 [20.25, 20.34]+24%2,017
EpiLoRA, searched17.75 [17.70, 17.77]+8%2,124

Table 28. Across two H100s, EpiLoRA leads vLLM’s kernels tuned for the tensor-parallel shards at batch 1. Qwen2.5-72B-Instruct with vLLM’s online FP8 at two-way tensor parallelism, one random rank-64 adapter; W1: time per output token at batch 1 (mean of three starts and their range) and its increase over the engine without LoRA (16.39 ms); W2: throughput with one adapter (without LoRA 2,877 tokens/s). EpiLoRA runs the decode path on tiles searched for the shards; with 8 tenants vLLM delivers 1,567 tokens/s against EpiLoRA’s 1,549.

PathW1 (ms) ↓over no LoRA ↓W2, one (tok/s) ↑W2, 8 (tok/s) ↑
vLLM, tuned for the 72B28.20 [28.06, 28.27]+21%1,042980
vLLM, tuned, two streams29.18 [29.17, 29.19]+25%994926
EpiLoRA, timed dispatch (searched tiles)26.50 [26.48, 26.51]+13%1,038974
EpiLoRA, decode path forced27.32 [27.31, 27.32]+17%1,041975

Table 29. On one H200, EpiLoRA’s timed dispatch serves the 72B fastest at batch 1. Qwen2.5-72B-Instruct with vLLM’s online FP8, random rank-64 adapters, vLLM’s LoRA kernels tuned for this model on this GPU; W1 as in Table 28 (without LoRA 23.35 ms), W2 with one adapter and with 8 (without LoRA 1,320 tokens/s). Forcing the decode path on every projection is 1.03× slower than the timed dispatch, which serves a third of the projections with vLLM’s kernels. An earlier job with the same configurations measured 28.00 against 26.80 ms at batch 1 (0.96×, the value in Table 2) and, with 8 tenants, 985 against 951 tokens/s.

LoRA RL

Every configuration (Figure 13, Table 30 and Table 31). Final accuracy of each RL configuration against bf16 training on the same GPU, with its cost; Table 32 tests each comparison for equivalence within ±5 points, also paired by seed and on one common engine.

Final accuracy minus bf16 training for FP8 rollouts

Figure 13. Figure 13. FP8 rollouts end close to bf16 training when they are corrected or matched. Final accuracy minus that of bf16 training on the same GPU (in each heading), with 95% confidence intervals and the number of runs; shaded: ±5 points; dotted: before RL. Gray: other systems’ recipes; KV freed: the engine releases its KV cache during training; †compared against this reference after the data; the 27B’s two- and one-copy rows pool every completed run, as in Table 36. Accuracy on OlympiadBench and Minerva pooled, except the 27B on the H100, which also includes MATH-500 (Table 31).

Engine, LoRA pathW1 (ms) ↓W2 (tok/s) ↑W1 ↓W2 ↑W1 ↓W2 ↑
1 adapter (%)32 adapters (%)
SGLang 0.5.9, csgmv4.6910,192+74.9−45.3+74.9−45.3
SGLang 0.5.9, triton4.6910,192+78.4−79.1+78.1−79.0
vLLM 0.28, default kernels4.1110,742+67.9−28.8+106.9−56.6
vLLM 0.28, dual-stream4.1110,742+52.1−30.8+90.7−55.9
ours, decode path4.1110,742+10.4−28.6+12.5−44.4

Table 19. On the H200, EpiLoRA has 0.55× SGLang’s batch-1 latency and 1.07× its throughput with 32 tenants. The protocol of Table 18 on one H200 NVL; one start per configuration. SGLang’s LoRA path again adds about the same time per step with one adapter or 32 (3.5 ms at batch 1).

The decomposition on a second model (Table 33). On Qwen3-4B, a dense model, with five of its math RL adapters, the ordering of Figure 7 holds: rounding activations leaves 6.3–7.4× the bf16 pair’s KL, most of it the activations’ rounding itself (the fake-quant control), and the unrounded engines retain 1.1–1.6×.

The mismatch over training (Table 34). Two hundred steps cannot show a collapse that comes later, but the mismatch’s trend can: with the matched engine and no correction it stays at the level of a bf16 engine and trainer, while uncorrected W8A8 rollouts more than double theirs.

Where the GPU-hours go on the H100 (Table 35). Generation takes most of each step, and the matched engine’s extra cost on the H100 is almost all in generation.

What each part costs and buys (Table 36 and Table 37; Figure 9 shows the memory of each placement).

Software and Hardware

Tuning vLLM’s LoRA kernels. For each model and GPU, we record the shape of every shrink and expand call of a serving run and time, for each shape, rank (64, for some models also 16), slot count (1 and 32; 8 for the 72B) and token count (1 to 8,192), vLLM’s default configuration and the 12 to 47 others that run (block sizes, split-K, warps) with CUDA graphs and the L2 cache flushed; we keep the fastest whose output matches the default’s, a median 1.21.2–1.4×1.4\times faster than the default over 140 to 400 keys per model and GPU. vLLM looks the files up by GPU name: for Qwen3.5-9B, the H200 and GH200 use the H100’s, as we did not tune on those GPUs (on Qwen3-8B, tuning on the H200 itself kept the H100’s configuration for 112 of 160 keys). Software: vLLM 0.28.0, PyTorch 2.13.0, Triton 3.7.1, FlashInfer 0.6.16, Transformers 5.16.1, SGLang 0.5.9 and TensorRT-LLM in their own environments; CUDA 12.8 and 13.1. Hardware: NVIDIA L40S (48 GB, Ada); H100 SXM (80 GB), H200 NVL (141 GB) and GH200 (96 GB, Hopper); A100 (80 GB, RL only); a 2g.48gb MIG slice of an RTX PRO 6000 (Blackwell).

References

  1. [1]Brüel-Gabrielsson, R., Zhu, J., Bhardwaj, O., Choshen, L., Greenewald, K., Yurochkin, M., and Solomon, J. Compress then serve: Serving thousands of LoRA adapters with little overhead. In Proceedings of the 42nd International Conference on Machine Learning (ICML), pp. 18062–18095, 2025. arXiv:2407.00066.
  2. [2]Chang, L.-W., Bao, W., Hou, Q., Jiang, C., Zheng, N., Zhong, Y., Zhang, X., Song, Z., Yao, C., Jiang, Z., Lin, H., Jin, X., and Liu, X. FLUX: Fast software-based communication overlap on GPUs through kernel fusion. arXiv preprint arXiv:2406.06858, 2024.arxiv.org/abs/2406.06858
  3. [3]Chen, F., Jiang, Z., Wei, Z., Zhang, Z., Li, D., Zhang, C., Lin, H., and Venkatamaran, S. Towards full pipeline FP8 reinforcement learning for LLMs. arXiv preprint arXiv:2609.22870, 2026a.arxiv.org/abs/2609.22870
  4. [4]Chen, H., Ruan, L., Xu, Z., Li, Y., Chen, X., Leng, J., He, B., Guo, M., and Sun, S. InfiniLoRA: Disaggregated multi-LoRA serving for large language models. arXiv preprint arXiv:2604.07173, 2026b.arxiv.org/abs/2604.07173
  5. [5]Chen, L., Ye, Z., Wu, Y., Zhuo, D., Ceze, L., and Krishnamurthy, A. Punica: Multi-tenant LoRA serving. In Proceedings of Machine Learning and Systems (MLSys), pp. 1–13, 2024. arXiv:2310.18547.arxiv.org/abs/2310.18547
  6. [6]Dettmers, T., Pagnoni, A., Holtzman, A., and Zettlemoyer, L. QLoRA: Efficient finetuning of quantized LLMs. In Advances in Neural Information Processing Systems, pp. 10088–10115, 2023.arxiv.org/abs/2305.14314
  7. [7]Frantar, E., Castro, R. L., Chen, J., Hoefler, T., and Alistarh, D. MARLIN: Mixed-precision auto-regressive parallel inference on large language models. In Proceedings of the 30th ACM SIGPLAN Annual Symposium on Principles and Practice of Parallel Programming (PPoPP), pp. 239–251, 2025.
  8. [9]Gale, T., Narayanan, D., Young, C., and Zaharia, M. MegaBlocks: Efficient sparse training with mixture-of-experts. In Proceedings of Machine Learning and Systems (MLSys), pp. 288–304, 2023.arxiv.org/abs/2211.15841
  9. [11]Guo, H., Greengard, P., Xing, E. P., and Kim, Y. LoRA: Low-rank plus quantized matrix decomposition for efficient language model finetuning. In International Conference on Learning Representations (ICLR), 2024.arxiv.org/abs/2311.12023
  10. [13]He, H. and Thinking Machines Lab. Defeating nondeterminism in LLM inference. Thinking Machines Lab: Connectionism, 2025.DOI
  11. [15]Hu, J., Wu, X., Zhu, Z., Xianyue, Wang, W., Zhang, D., and Cao, Y. OpenRLHF: An easy-to-use, scalable and high-performance RLHF framework. arXiv preprint arXiv:2405.11143, 2024.arxiv.org/abs/2405.11143
  12. [17]Huang, W., Ge, Y., Yang, S., Xiao, Y., Mao, H., Lin, Y., Ye, H., Liu, S., Cheung, K. C., Yin, H., Lu, Y., Qi, X., Han, S., and Chen, Y. YeBRL: Beyond efficiency – quantization-enhanced reinforcement learning for LLMs. arXiv preprint arXiv:2510.11696, 2025.arxiv.org/abs/2510.11696
  13. [18]Iliakopoulou, N., Stojkovic, J., Alverti, C., Xu, T., Franke, H., and Torrellas, J. Chameleon: Adaptive caching and scheduling for many-adapter LLM inference environments. In Proceedings of the 58th IEEE/ACM International Symposium on Microarchitecture (MICRO), pp. 217–231, 2025.DOI
  14. [20]Kong, R., Li, Q., Fang, X., Feng, Q., He, Q., Dong, Y., Wang, W., Li, Y., Kong, L., and Liu, Y. LoRA-Switch: Boosting the efficiency of dynamic LLM adapters via system-algorithm co-design. arXiv preprint arXiv:2405.17741, 2024.
  15. [21]Kwon, W., Li, Z., Zhuang, S., Sheng, Y., Zheng, L., Yu, C. H., Gonzalez, J. E., Zhang, H., and Stoica, I. Efficient memory management for large language model serving with PagedAttention. In Proceedings of the 29th Symposium on Operating Systems Principles (SOSP), pp. 611–626, 2023.arxiv.org/abs/2309.06180
  16. [22]Lewkowycz, A., Andreassen, A., Dohan, D., Dyer, E., Michalewski, H., Ramasesh, V., Slone, A., Anil, C., Schlag, I., Gutman-Solo, T., Wu, Y., Neyshabur, B., Gur-Ari, G., and Misra, V. Solving quantitative reasoning problems with language models. In NeurIPS, pp. 3843–3857, 2022.arxiv.org/abs/2206.14858
  17. [23]Li, M., Lin, Y., Zhang, Z., Cai, T., Li, X., Guo, J., Xie, E., Meng, C., Zhu, J.-Y., and Han, S. SVDQuant: Absorbing outliers by low-rank components for 4-bit diffusion models. In International Conference on Learning Representations (ICLR), 2025. arXiv:2411.05007.arxiv.org/abs/2411.05007
  18. [27]Lin, J., Tang, J., Tang, H., Yang, S., Chen, W.-M., Wang, W.-C., Xiao, G., Dang, X., Gan, C., and Han, S. AWQ: Activation-aware weight quantization for on-device LLM compression and acceleration. In Proceedings of Machine Learning and Systems, pp. 87–100, 2024.DOI
  19. [28]Lin, Y., Tang, H., Yang, S., Zhang, Z., Xiao, G., Gan, C., and Han, S. QServe: W4A8KV4 quantization and system co-design for efficient LLM serving. In Proceedings of Machine Learning and Systems, 2025.
  20. [29]Liu, J., Xiao, G., Li, K., Lee, J. D., Han, S., Dao, T., and Cai, T. BitDelta: Your fine-tune may only be worth one bit. In Advances in Neural Information Processing Systems, pp. 13579–13600, 2024.arxiv.org/abs/2402.10193
  21. [30]Liu, L., Yao, F., Zhang, D., Dong, C., Shang, J., and Gao, J. FlashRL: 8-bit rollouts, full power RL. https://fengyao.notion.site/flashr-rl, 2025.
  22. [31]Llama Team, AI @ Meta. The Llama 3 herd of models. arXiv preprint arXiv:2407.21783, 2024.
  23. [32]Marek, M. and Ryabinin, M. Score centering stabilizes off-policy reinforcement learning. arXiv preprint arXiv:2609.20807, 2026.arxiv.org/abs/2609.20807
  24. [33]Mei, Z., Fu, W., Li, K., Wang, G., Zhang, H., and Wu, Y. ReaL: Efficient RLHF training of large language models with parameter reallocation. In Proceedings of Machine Learning and Systems (MLSys), 2025.arxiv.org/abs/2406.14088
  25. [35]Miahi, E. and Belilovsky, E. Understanding and exploiting weight update sparsity for communication-efficient distributed RL. arXiv preprint arXiv:2602.03839, 2026.arxiv.org/abs/2602.03839
  26. [36]Miao, X., Oliaro, G., Cheng, X., Wu, M., Unger, C., and Jia, Z. FlexLLM: A system for co-serving large language model inference and parameter-efficient finetuning. arXiv preprint arXiv:2402.18789, 2024.
  27. [37]Micikevicius, P., Stosic, D., Burgess, N., Cornea, M., Dubey, P., Grinsztajn, R., Ha, S., Heinecke, A., Judd, P., Kamalu, J., Mellempudi, N., Oberman, S., Shoeybi, M., Siu, M., and Wu, H. FP8 formats for deep learning. arXiv preprint arXiv:2209.05433, 2022.arxiv.org/abs/2209.05433
  28. [38]Mind Lab. MinT: Managed infrastructure for training and serving millions of LLMs. arXiv preprint arXiv:2605.13779, 2026.
  29. [39]Mirzaei, A. R., Wen, Y., Cao, Y., and Mou, L. LoRAQuant: Mixed-precision quantization of LoRA to ultra-low bits. arXiv preprint arXiv:2510.26690, 2025.arxiv.org/abs/2510.26690
  30. [40]NVIDIA. Pretraining large language models with NVFP4. arXiv preprint arXiv:2509.25149, 2025.arxiv.org/abs/2509.25149
  31. [42]Pati, S., Aga, S., Islam, M., Jayasena, N., and Sinclair, M. D. T3: Transparent tracking & triggering for fine-grained overlap of compute & collectives. In Proceedings of the ACM International Conference on Architectural Support for Programming Languages and Operating Systems (ASPLOS), pp. 1146–1164, 2024.arxiv.org/abs/2401.16677
  32. [43]Ping, B., Wang, S., Wang, H., Han, X., Xu, Y., Yan, Y., Chen, Y., Chang, B., Liu, Z., and Sun, M. Delta-CoMe: Training-free delta-compression with mixed-precision for large language models. In Advances in Neural Information Processing Systems, pp. 31056–31077, 2024.arxiv.org/abs/2406.08903
  33. [44]Qiu, Z., Yu, S., Zhang, J., Zhang, S., Huang, X., Yang, J., and Lai, J. FP8-RL: A practical and stable low-precision stack for LLM reinforcement learning. arXiv preprint arXiv:2601.18150, 2026.
  34. [45]Qwen Team. Qwen3 technical report. arXiv preprint arXiv:2505.09388, 2025.
  35. [46]Qwen Team. Qwen3.5. https://huggingface.co/Qwen/Qwen3.5-9B, 2026a.
  36. [47]Qwen Team. Qwen3.8. https://huggingface.co/Qwen/Qwen3.8-27B, 2026b.
  37. [48]Rouhani, B. D., Zhao, R., More, A., Hall, M., Khodamoradi, A., Deng, S., Choudhary, D., Cornea, M., Dellinger, E., Denolf, K., Dusan, S., Elango, V., Golub, M., Heinecke, A., James-Roxby, P., Jani, D., Kolhe, G., Langhammer, M., Li, A., Melnick, L., Mesmakhosroshahi, M., Rodriguez, A., Schulte, M., Shafipour, R., Shao, L., Siu, M., Dubey, P., Micikevicius, P., Naumov, M., Verrilli, C., Wittig, R., Burger, D., and Chung, E. Microscaling data formats for deep learning. arXiv preprint arXiv:2310.10537, 2023.arxiv.org/abs/2310.10537
  38. [49]Schulman, J. and Thinking Machines Lab. LoRA without regret. Thinking Machines Lab: Connectionism, 2025.DOI
  39. [50]Shao, Z., Wang, P., Zhu, Q., Xu, R., Song, J., Bi, X., Zhang, H., Zhang, M., Li, Y. K., Wu, Y., and Guo, D. DeepSeek-Math: Pushing the limits of mathematical reasoning in open language models. arXiv preprint arXiv:2402.03300, 2024.arxiv.org/abs/2402.03300
  40. [51]Sheng, G., Zhang, C., Ye, Z., Wu, X., Zhang, W., Zhang, R., Peng, Y., Lin, H., and Wu, C. HybridFlow: A flexible and efficient RLHF framework. In Proceedings of the European Conference on Computer Systems (EuroSys), pp. 1279–1297, 2025.DOI
  41. [52]Sheng, Y., Cao, S., Li, D., Hooper, C., Lee, N., Yang, S., Chou, C., Zhu, B., Zheng, L., Keutzer, K., Gonzalez, J. E., and Stoica, I. SLoRA: Scalable serving of thousands of LoRA adapters. In Proceedings of Machine Learning and Systems (MLSys), pp. 296–311, 2024.
  42. [53]Thinking Machines Lab. Tinker: a training API for fine-tuning open-weight models. https://thinkingmachines.ai/tinker/, 2025.
  43. [55]Unsloth AI. Reinforcement learning (RL) guide. Unsloth documentation, https://unsloth.ai/docs/basics/reinforcement-learning-rl-guide, 2025b.
  44. [58]Wang, S., Wei, J., Sabne, A., Davis, A., Ilbeyi, B., Hechtman, B., Chen, D., Murthy, K. S., Maggioni, M., Zhang, Q., Kumar, S., Guo, T., Xu, Y., and Zhou, Z. Overlap communication with dependent computation via decomposition in large deep learning models. In Proceedings of the ACM International Conference on Architectural Support for Programming Languages and Operating Systems (ASPLOS), pp. 93–106, 2023.DOI
  45. [59]Wang, S., Ren, R., and Gui, L. ForkKV: Scaling multi-LoRA agent serving via copy-on-write disaggregated KV cache. arXiv preprint arXiv:2604.06370, 2026a.arxiv.org/abs/2604.06370
  46. [60]Wang, X., Yu, H., Zhuge, Z., Mao, B., Li, Z., Feng, J., Luo, Y., Zhang, Y., Cao, Y., Zhang, M., Liu, D., and Zhang, J. TRACE: Rollout-guided quantization-aware training for FP4 reinforcement learning of MoE language models. arXiv preprint arXiv:2610.07767, 2026b.arxiv.org/abs/2610.07767
  47. [61]Wen, Y. and Chaudhuri, S. Batched low-rank adaptation of foundation models. In International Conference on Learning Representations (ICLR), 2024.arxiv.org/abs/2312.05677
  48. [64]Xia, Y., Fu, F., Zhang, W., Jiang, J., and Cui, B. Efficient multi-task LLM quantization and serving for multiple LoRA adapters. In Advances in Neural Information Processing Systems (NeurIPS), pp. 63686–63714, 2024.DOI
  49. [66]Yao, X., Hu, Q., and Klimovic, A. DeltaZip: Efficient serving of multiple full-model-tuned LLMs. In Proceedings of the European Conference on Computer Systems (EuroSys), pp. 110–127, 2025.
  50. [68]Yu, Q., Zhang, Z., Zhu, R., Yuan, Y., Zuo, X., Yue, Y., Dai, W., Fan, T., Liu, G., Liu, L., Liu, X., Lin, H., Lin, Z., Ma, B., Sheng, G., Tong, Y., Zhang, C., Zhang, M., Zhang, W., Zhu, H., Zhu, J., Chen, J., Chen, J., Wang, C., Yu, H., Song, Y., Wei, X., Zhou, H., Liu, J., Ma, W.-Y., Zhang, Y.-Q., Yan, L., Qiao, M., Wu, Y., and Wang, M. DAPO: An open-source LLM reinforcement learning system at scale. arXiv preprint arXiv:2503.14476, 2025.arxiv.org/abs/2503.14476
  51. [72]Zheng, L., Yin, L., Xie, Z., Sun, C., Huang, J., Yu, C. H., Cao, S., Kozyrakis, C., Stoica, I., Gonzalez, J. E., Barrett, C., and Sheng, Y. SGLang: Efficient execution of structured language model programs. In Advances in Neural Information Processing Systems, pp. 62557–62583, 2024.arxiv.org/abs/2312.07104
  52. [73]Zhou, J., Shao, W., Zheng, L., Fan, Y., and Wong, N. AIS: Adaptive importance sampling for quantized RL. arXiv preprint arXiv:2605.13907, 2026.arxiv.org/abs/2605.13907
  53. [74]Zhu, K., Gao, Y., Zhao, Y., Zhao, L., Zuo, G., Gu, Y., Xie, D., Tang, T., Xu, Q., Ye, Z., Kamahori, K., Lin, C.-Y., Wang, Z., Wang, S., Krishnamurthy, A., and Kasicki, B. NanoFlow: Towards optimal large language model serving throughput. arXiv preprint arXiv:2408.12757, 2024.arxiv.org/abs/2408.12757
  54. [75]Zhu, Z., Su, Q., Ding, Y., Song, K., Wang, S., and Pekhi-menka, G. LoRAFusion: Efficient LoRA fine-tuning for LLMs. In Proceedings of the European Conference on Computer Systems (EuroSys), pp. 586–604, 2026.DOI
  55. [76]Zhuge, Z., Yu, H., Wang, X., Li, Z., Cao, Y., Liu, D., and Zhang, J. QUADS: Stabilizing NVFP4 reinforcement learning for MoE via QUantization-error alignment across dual sides. arXiv preprint arXiv:2607.15810, 2026.arxiv.org/abs/2607.15810

Paper details

vlnc:W_5zjnt5pa
v1 · latestHistory (1)

published · Fresh — added to the Hub within the last 30 days

added ·

Usage

views · 0

downloads · 0

A view counts once per browser per day, a download each time the PDF is requested.

CC BY-SA 4.0

Original source

  • Computer Science

Not recorded for this paper · View statement →

Looking for related papers…
Contents