In 2018 I hand-wrote a C++ deep learning framework so I'd never pad a batch. In 2023 LLM serving landed on the same structure. A developer who built InsNet, a ~21,000-line C++14 deep learning library with 4,828 lines of hand-written CUDA kernels, starting in 2018, describes how its padding-free dynamic batching design anticipated the flat, padding-free token streams with per-sequence offsets now used by modern LLM serving engines like vLLM. The library represents each value as a flat buffer with only element count and width, eliminating batch dimensions, max lengths and mask tensors, a bet the author says was early but aimed at the wrong layer of the stack. There's a sentence in the README of a library I wrote that I've been thinking about lately: "To summarize, we believe that Padding-free Dynamic Batching is the feature that NLPers will dive into but is surprisingly not supported by today's deep learning libraries ." I wrote that around 2021, about InsNet https://github.com/chncwang/InsNet , a C++14 deep learning library I'd been building since 2018. Then transformers ate the field, everyone padded their batches to rectangles like they always had, and I moved on. Two years later, vLLM launched, "continuous batching" became the load-bearing idea of the entire LLM serving industry, and the input to a modern inference engine became — a flat, padding-free token stream with per-sequence offsets riding alongside as data. I wasn't wrong. I was early, and I was aiming at the wrong layer of the stack. This post is the story of that bet: how a C++ library made padding disappear, how the canonical prior art DyNet made a subtly different choice at the same fork, and how the modern serving stack vLLM + FlashAttention ended up rediscovering the same trick — with receipts from all three codebases, because last time I compared engines from memory people rightly asked for sources, and reading the actual code is where all the good surprises live anyway. In 2018 I was a master's student doing NLP research — the last years before transformers swallowed the field. The workhorses were still RNNs and LSTMs, and the frontier I found interesting was the models whose computation graph changed shape with every single input : tree-LSTMs folded along a sentence's parse tree, transition-based parsers emitting a different sequence of stack operations for each sentence, hierarchical encoders running one sub-model per sentence and another over the document. Two examples in a batch almost never had the same shape — one had 7 tokens, its neighbor had 212, and their tree structures didn't line up at all. Tree-LSTM, transition-based parser, hierarchical encoder — three inputs, three graph shapes, no shared rectangle. The standard answer was padding: extend everything to the longest sequence in the batch, add a mask tensor, and burn FLOPs computing values you'd immediately multiply by zero. For flat same-ish-length batches this is a mild tax. For instance-dependent structures it's obscene — and worse than the wasted compute was the wasted thinking : every model became two models, the one you meant and the one that handles the mask. I wanted to write the model for one instance and have the library figure out the batching. So I wrote a library: InsNet. It grew out of N3LDG — an earlier dynamic-computation-graph NLP library I helped build and first-authored the 2019 paper for — reworked into about 21,000 lines of first-party C++, of which 4,828 lines are one file of hand-written CUDA kernels. InsNet's core representation makes padding impossible rather than optional. A value's data lives in one flat buffer, and its shape is just two integers: the total element count and the width. In an NLP model every value is a 2-D matrix — d rows the hidden size by some number of columns, and only the column count ever changes. A transformer holds a whole sentence at once, so a value is a d×L matrix, one column per token; an RNN steps through the sentence one token at a time, so each value is a single column — a d×1 vector. Same d rows either way; the width is the only thing that moves, which is exactly why two integers pin the whole shape. There isn't even a stored length — divide the buffer's size by the hidden dimension and the token count falls out. A value is never a batch, L max, d slice with a mask; it's a matrix as wide as the work in front of it. No batch dimension anywhere in the type system, no max length, no mask tensor. You cannot pad because there is nothing to pad to . Padding stretches every sentence to the longest and burns compute on the gaps 29% here ; InsNet keeps each value exactly as wide as its sentence. The interesting part is how batching happens with no batch dimension. InsNet uses lazy execution: your model code builds a graph of small nodes, and nothing runs until you call forward . At that point the executor repeatedly takes the current wave of ready nodes Kahn's algorithm — every node whose inputs are all computed and buckets the wave by a type signature : // graph.h — the ready set, bucketed by signature typedef std::unordered map