License notice This project is source-available under the PolyForm Noncommercial License 1.0.0 β free for personal, research, teaching, and internal-evaluation use.
Any commercial use, redistribution as part of a commercial product, or paid hosted deployment requires a separate commercial license. See COMMERCIAL.md or contact https://github.com/AnubhabBanerjee.
Unauthorized commercial use or redistribution is a violation of the license terms.
Read the full deep-dive here: https://towardsdatascience.com/i-built-a-c-backend-so-my-gpu-would-stop-eating-air/
Zero-padding, asynchronous First-Fit Decreasing (FFD) bin packing for extreme-context LLM workloads (e.g., AI patent evaluation). Bypasses the GIL and CPU memory bottlenecks by streaming tokenized sequences into a C++ backend, packing them dynamically based on empirical VRAM hardware limits, and passing them to FlashAttention-2 via zero-copy pinned memory (cudaHostAlloc).
This repo is a high-performance inference infrastructure slice: Python handles dynamic VRAM discovery and generic stream tokenization, while a compiled C++ backend (exposed via PyBind11) manages thread-safe sequence queuing, 16-token Tensor Core alignment, and exact hardware-bounded bin packing without ever duplicating data in host RAM.
In extreme-context LLM inference, variable-length documents cause massive padding waste or fatal Out-Of-Memory (OOM) errors with standard item-count batching. Kubernetes or native PyTorch schedulers do not protect against dynamic attention workspace bloat.
This repository implements a systems-level paradigm shift: it transitions from "Batch Size" batching to "VRAM Capacity" packing. By micro-padding to hardware boundaries and dynamically grouping independent sequences into a strictly enforced, hardware-defined byte limit, it guarantees zero OOMs and ensures every forward pass utilizes maximum GPU silicon.
WarpGroup was evaluated across entry-level and production hardware on highly variable text corpora. Packed bins are 1-D token ribbons plus cu_seqlens cut-points. VarlenModelWrapper now routes those cut-points into flash_attn_varlen_func, so documents in the same bin do not attend to each other. Load the Hugging Face model with attn_implementation="flash_attention_2" (see main_working_file.py / example_runs/optimized_run.py).
Benchmark note: The tables below were measured with the older packed path + SDPA (standard PyTorch attention). In that configuration
cu_seqlensreset position IDs only; attention still treated the bin as one sequence. Those numbers are throughput/memory, not a claim of identical logits vs the padded baseline. Re-runoptimized_run.pyafter this change to measure the FA2-varlen stack.
Hardware: NVIDIA H100 (80 GB) | Model: Qwen2.5-7B-Instruct Stack: WarpGroup tuned stack + SDPA (Standard PyTorch attention) Dataset: 400 PDFs (high-variance skew: 45β130-word documents interleaved with 1820β2000-word documents)
Standard batching collapses when faced with high-variance document lengths. In this stress test, the baseline Hugging Face pipeline was forced to pad over 818,000 tokens just to maintain rectangular tensor shapes, inflating its dynamic memory usage and cutting throughput in half.
WarpGroup effortlessly absorbed the variance, using 1D continuous tensors to achieve a 2.08Γ throughput multiplier and a ~62% reduction in dynamic memory overhead.
| Metric | Baseline (HF) | WarpGroup | Improvement |
|---|---|---|---|
| Padding overhead | 48.41% | 0.55% | 47.9 pp absolute reduction |
| Throughput | 14,713 tok/s | 30,672 tok/s | 2.08Γ higher |
| Peak VRAM | 19.88 GB | 16.50 GB | 17% lower (3.38 GB saved) |
| Dynamic VRAM (est.)* | ~5.38 GB | ~2.00 GB | ~62% lower dynamic memory |
| Wall clock | 28.69 s | 13.76 s | 2.08Γ faster |
*Architecture note on dynamic VRAM: A 7B-parameter model in bf16/fp16 requires ~14.5β15 GB of static VRAM for weights. The dynamic memory (activations / KV cache) is where the padding penalty occurs. WarpGroup processed the exact same sequence of real tokens while shedding over 3 GB of wasted dynamic allocation.
Dataset: 300 PDFs (uniform distribution: 50β1900 words)
Even on a less adversarial, uniformly distributed dataset, WarpGroup fully saturates the Hopper Tensor Cores without the drag of padded tokens, yielding a 70% increase in useful-token throughput over the baseline.
| Metric | Baseline (HF) | WarpGroup | Improvement |
|---|---|---|---|
| Padding overhead | 36.20% | 0.67% | 35.5 pp absolute reduction |
| Throughput | 18,047 tok/s | 30,700 tok/s | 1.70Γ higher |
| Wall clock | 17.86 s | 10.50 s | 1.70Γ faster |
Hardware: NVIDIA GeForce GTX 1080 (8 GB, SM 6.1) | Model: SmolLM2-360M-Instruct Stack: WarpGroup packed 1-D bins + SDPA (not FlashAttention-2)
Pascal (SM 6.1) cannot run FlashAttention-2. This run therefore never called flash_attn_varlen_func. Packed documents shared one SDPA sequence, so later documents could attend to earlier ones. 5.89Γ is a throughput/memory number on that broken attention configuration, not a correct-varlen result. The current attn_implementation="flash_attention_2" path will not run on this GPU.
By packing continuous 1D tensors, WarpGroup still avoided pad tokens and used less VRAM than the rectangular baseline β that part of the table is the padding story, not a correctness claim.
| Metric | Baseline (HF) | WarpGroup | Improvement |
|---|---|---|---|
| Padding overhead | 41.13% | 0.00% | Baseline padding eliminated |
| Throughput | 405 tok/s | 2,387 tok/s | 5.89Γ higher (SDPA, cross-doc attention; not FA2) |
| Peak VRAM | 2.85 GB | 1.86 GB | 35% lower |
When MAX_LEN constraints are removed, standard batching attempts to allocate memory for the theoretical maximum grid (batch_size Γ longest_sequence), rapidly causing CUDA Out-Of-Memory errors on variable text.
- Baseline: Crashed (
torch.OutOfMemoryError: Tried to allocate 30.00 GiB). - WarpGroup: Completed successfully (Peak VRAM: 3.60 GB). The Phase-0 autotune probes the GPU at startup and locks a strict hardware-aligned token budget; the bin packer rejects any forward pass that would exceed that budget.
python example_runs/run_vram_contrast_benchmark.py \
--model example_models/Qwen2.5-7B-Instruct \
--baseline-batch-size 8
python example_runs/random_pdf_generator.py --count 300 \
--tokens-min 50 --tokens-max 1900 --seed 42
python example_runs/baseline_run.py --pdf-dir example_runs/data/ \
--model example_models/Qwen2.5-7B-Instruct --batch-size 4
python example_runs/optimized_run.py --pdf-dir example_runs/data/ \
--model example_models/Qwen2.5-7B-InstructRaw JSON for every run is written under example_runs/results/ (and copied into example_runs/plots/<run_id>/ for archival). The Β§1 manifest is example_runs/results/vram_contrast_manifest.json.
The pipeline is a decoupled autotune β ingest β pack β execute graphβI/O is isolated in Python, while compute and memory mapping run concurrently in C++:
- Phase 0: Hardware Autotuning β
determine_vram_capacityempirically probes the GPU with synthetic sequences to find the physical VRAM limit for your specific model's attention workspace. - Phase 1: Generic Ingestion β Python generators yield strings (e.g., from JSONL), tokenize them to flat integers, and stream them via
submit_sequenceacross the PyBind11 boundary. - Phase 2: Async C++ Dispatch & Alignment β
async_dispatcher.cppcatches tokens in astd::dequeoutside the Python GIL. Sequences are micro-padded to 16-token intervals to prevent Tensor Core stalling. - Phase 3: FFD Bin Packing β A background thread sorts the queue and packs sequences into a pinned memory pool (
cudaHostAlloc) using a First-Fit Decreasing algorithm up to the empirical VRAM limit. - Phase 4: Zero-Copy Wrapping β
torch::from_blobwraps the C++ memory in a PyTorch metadata shell. The GPU's DMA controller pulls the unpadded sequences directly across the PCIe bus. - Phase 5: Varlen attention β
VarlenModelWrapperrebuilds per-documentposition_idsfromcu_seqlensand patches FlashAttention-2 so each layer callsflash_attn_varlen_funcwith those cuts. Hugging Facemodel.forward()still does not takecu_seqlens; the wrapper is the bridge.
| Layer | Role |
|---|---|
| C++17 / PyBind11 | Thread-safe queueing, memory pooling, and GIL-free background execution. |
| CUDA Runtime API | Explicit page-locked host memory allocation (cudaHostAlloc). |
| PyTorch 2.5+ | Tensor metadata wrapping, model weights, and graph execution. |
FlashAttention-2 (flash-attn) |
Variable-length attention via flash_attn_varlen_func (requires attn_implementation="flash_attention_2"). |
| Hugging Face Transformers | Base model loading and tokenizer. VarlenModelWrapper injects cu_seqlens into FA2 layers. |
| Item | Value |
|---|---|
| OS | Ubuntu 24.04 |
| GPU | NVIDIA H100 (80GB) or equivalent |
| NVIDIA driver | 535+ (CUDA 12.2+) |
| Python | 3.12+ |
| Compiler | GCC 9.0+ / CMake 3.18+ |
| PyTorch | v2.5+ (cu121) |
- Linux host for compiling the C++ backend and running the PyTorch loops.
- CUDA Toolkit installed and accessible in
$PATHfor compiling the C++ extensions. - Python 3.12+ with a virtual environment.
- A target LLM supported by Hugging Face. Packed inference requires
flash-attnandattn_implementation="flash_attention_2"socu_seqlensreachflash_attn_varlen_func.
From the repository root, set up your Python environment and compile the C++ backend:
python3.12 -m venv .venv
source .venv/bin/activate
pip install --upgrade pip
pip install -r requirements.txt
pip install -e .
# Compile the PyBind11 C++ backend
mkdir build && cd build
cmake ..
make -j4
cp warpgroup_backend*.so ..
cd ..
End-to-end VRAM autotuning, ingestion, and background packing:
python3 main_working_file.py
| Artifact | Path |
|---|---|
| Python Entry Point | main_working_file.py |
| PyTorch DataLoader Logic | streaming_dataloader.py |
| C++ Engine Bindings | csrc/bindings.cpp |
| C++ Async Queue | csrc/core/async_dispatcher.h, csrc/core/async_dispatcher.cpp |
| Built Extension | warpgroup_backend.cpython-312-x86_64-linux-gnu.so |
βββ README.md # Project overview + benchmarks
βββ README_vram_contrast_dataset.md # β see example_runs/ (note: this file is under example_runs/)
βββ CMakeLists.txt # PyBind11 + CUDA build directives
βββ setup.py # `pip install -e .` entry point
βββ requirements.txt # torch, transformers, flash-attn, pymupdf, ...
βββ .gitignore
βββ overview_image.png # README hero image
β
βββ main_working_file.py # End-to-end driver
βββ streaming_dataloader.py # Phase-0 autotuner + VarlenModelWrapper (FA2 varlen + position reset)
βββ reader_and_tokennizer.py # PDF reader + tokenizer helpers
β
βββ warpgroup/ # Importable Python package
β βββ __init__.py
β βββ dataloader.py
β βββ reader.py
β
βββ csrc/ # C++17 / PyBind11 backend
β βββ bindings.cpp # PyBind11 Python β C++ boundary
β βββ core/
β β βββ async_dispatcher.cpp # GIL-free background worker
β β βββ bin_packer.cpp # First-Fit Decreasing packing + 16-token TC alignment
β β βββ memory_pool.cpp # cudaHostAlloc pinned memory + torch::from_blob handoff
β βββ include/
β βββ async_dispatcher.h
β βββ bin_packer.h
β βββ memory_pool.h
β
βββ example_runs/ # Reproducible benchmark harness
βββ README_example.md
βββ README_vram_contrast_dataset.md # Notes for the interleaved long/short corpus
βββ run_e2e.sh # CMake build + setup + baseline + optimized + report
βββ setup_experiment.py # Builds + copies warpgroup_backend.so into place
βββ random_pdf_generator.py # Uniform variable-length synthetic PDF corpus
βββ baseline_run.py # Standard Hugging Face padded-batch baseline
βββ optimized_run.py # WarpGroup stack (FFD + zero-copy + FA2 varlen)
βββ generate_report.py # Builds bar charts into plots/<run_id>/
βββ build_results_docx.py # Compiles every run into RESULTS.docx
βββ RESULTS.docx # Word-format results dossier
βββ plots/ # PNG bar charts per run (PNGs only, no JSONs in zip)
βββ e2e_smollm_300pdf/
β βββ padding_overhead.png
β βββ throughput_tokens.png
β βββ time_comparison.png
β βββ vram_comparison.png
βββ run_A_maxlen2048_dense/{padding_overhead,throughput_tokens,time_comparison,vram_comparison}.png
βββ run_C_maxlen2048_variable/{padding_overhead,throughput_tokens,time_comparison,vram_comparison}.png
βββ run_D_varlen_qwen_tuned/{padding_overhead,throughput_tokens,time_comparison,vram_comparison}.png
βββ run_vram_contrast_corpus/{padding_overhead,throughput_tokens,time_comparison,vram_comparison}.png
βββ run_vram_contrast_appendix/{padding_overhead,throughput_tokens,time_comparison,vram_comparison}.png
Active and planned hardening for the engine:
- Multi-GPU Sharding β Extend the dispatcher to manage multiple C++ queues, distributing dynamically sized bins across local GPU interconnects.
Built with PyBind11, PyTorch, and the NVIDIA CUDA Toolkit to optimize single-node LLM throughput by respecting silicon-level boundaries. Architecture inspired by the constraints of high-volume, asynchronous document evaluation pipelines.
