Back to Blog
Python

Python Transformers GPU and Batch Inference

python transformers gpu and batch inference: Learn how to run Hugging Face Transformers models on GPU with batched inputs to improve throughput and reduce inference la...

transformersgpubatch inferencehuggingfaceperformance
Illustration of a GPU chip with multiple data streams flowing into a transformer model, representing batch inference

When you run a Hugging Face Transformers model on a GPU, the hardware is designed to process many independent operations in parallel. If you feed it one input at a time, you leave most of that capacity unused. Python transformers GPU and batch inference is the practice of grouping multiple inputs into a single forward pass, which can dramatically increase throughput for workloads such as classification, summarization, or embedding generation.

This article shows how to load a model onto a GPU, tokenize inputs for batched processing, and run inference both through the high-level pipeline API and with the raw model classes. It also covers padding strategies, memory tradeoffs, and common errors that appear when you move from single-input to batched inference.

Why Batch Inference Matters on GPU

A GPU executes thousands of threads simultaneously. A single transformer forward pass uses those threads to compute matrix multiplications and attention scores for one sequence. When you send a single sequence, the GPU is underutilized because the batch dimension is 1. Batching increases the amount of work per forward pass, which amortizes the overhead of kernel launches and memory transfers.

The benefit is not linear. A batch of 32 inputs may take only slightly longer than a batch of 1, because the GPU can often process them in parallel. The practical result is that you can serve more requests per second without adding hardware. This is especially important for production services where latency per request is less critical than overall throughput.

However, batching also increases memory usage. The activations for every sequence in the batch are stored during the forward pass. If your batch is too large, you will run out of GPU memory. The right batch size depends on your model size, sequence length, and GPU memory, and it must be tuned empirically.

Loading a Model on GPU

The first step is to move the model to the GPU. The Transformers library uses PyTorch or TensorFlow as its backend. In PyTorch, you call .to("cuda") on the model. With the AutoModel classes, you load the model and then move it.

from transformers import AutoModel, AutoTokenizer model_name = "bert-base-uncased" tokenizer = AutoTokenizer.from_pretrained(model_name) model = AutoModel.from_pretrained(model_name) model.to("cuda")

After this, the model parameters are stored in GPU memory. You must also move the input tensors to the GPU before passing them to the model. The tokenizer does not move tensors automatically; you need to set return_tensors="pt" and then call .to("cuda") on the resulting tensors.

If you are using the pipeline API, you can pass device=0 to place the pipeline on the first GPU. This handles both the model and the tensors for you.

Tokenizing Inputs for Batched Inference

The tokenizer converts text to input IDs and attention masks. For batched inference, you need to tokenize multiple texts at once. The tokenizer call accepts a list of strings and returns a dictionary of tensors.

texts = [ "The quick brown fox jumps over the lazy dog.", "A journey of a thousand miles begins with a single step.", "To be or not to be, that is the question." ] inputs = tokenizer(texts, padding=True, truncation=True, return_tensors="pt")

padding=True pads all sequences to the same length within the batch. truncation=True truncates sequences longer than the model's maximum. The resulting inputs is a BatchEncoding object that contains input_ids, attention_mask, and possibly token_type_ids. These are PyTorch tensors with shape (batch_size, sequence_length).

If you are using a GPU, move these tensors to the device:

inputs = {k: v.to("cuda") for k, v in inputs.items()}

Now the tensors are ready for the model.

Using the Pipeline API for Batched Inference

The pipeline API abstracts away tokenization and model execution. It accepts a list of inputs and returns a list of outputs. To use a GPU, pass device=0.

from transformers import pipeline classifier = pipeline("sentiment-analysis", model="distilbert-base-uncased-finetuned-sst-2-english", device=0) results = classifier([ "I loved this movie!", "The plot was boring and predictable.", "An excellent performance by the lead actor." ])

The pipeline automatically tokenizes the list, batches the inputs, and runs inference. It also handles padding and truncation internally. This is the simplest way to get batched inference working.

The pipeline has a batch_size parameter that controls how many inputs are processed per forward pass. If you pass a large list, the pipeline will split it into batches of the given size. This is useful when you have many inputs and want to control memory usage.

results = classifier(texts, batch_size=16)

If you do not specify batch_size, the pipeline tries to infer a reasonable value from the model and device. For GPU, it often uses a larger batch size than for CPU.

Running Raw Model Inference with Batches

When you need more control, you can bypass the pipeline and call the model directly. This is useful for custom tasks, such as extracting embeddings or implementing a specific decoding strategy.

with torch.no_grad(): outputs = model(**inputs)

The outputs object contains the model's last hidden state and possibly other outputs depending on the model class. For AutoModel, the last_hidden_state has shape (batch_size, sequence_length, hidden_size). For sequence classification models, you get logits of shape (batch_size, num_labels).

To get embeddings for a batch of texts, you might pool the hidden states:

embeddings = outputs.last_hidden_state.mean(dim=1) # average pooling

This gives you a single vector per input. The key point is that the forward pass processes the entire batch at once.

When you use raw models, you must ensure the input tensors are on the same device as the model. If the model is on CUDA and the inputs are on CPU, you will get a runtime error. The inputs dictionary from the tokenizer can be moved with a simple loop, as shown earlier.

Handling Variable-Length Sequences with Padding

Transformer models expect fixed-length sequences within a batch. If your texts have different lengths, you need to pad the shorter ones. The tokenizer's padding=True does this automatically. By default, it pads to the longest sequence in the batch. You can also use padding="max_length" to pad to a fixed length, but that wastes memory if most sequences are shorter.

The attention mask tells the model which tokens are real and which are padding. The model ignores the padding tokens during attention computation. If you forget to pass the attention mask, the model will attend to padding tokens, which can degrade performance. Always include the attention mask when calling the model.

outputs = model(input_ids=inputs["input_ids"], attention_mask=inputs["attention_mask"])

For decoder-only models like GPT, you may also need to set pad_token_id to the tokenizer's eos_token_id if no pad token exists. This is a common source of errors.

Memory and Throughput Considerations

Batching increases memory usage because the model stores activations for all sequences in the batch. The relationship is roughly linear: a batch of 32 uses about 32 times the memory of a batch of 1, though some layers share memory. If you exceed the GPU's memory, you will get an out-of-memory (OOM) error.

To find a safe batch size, start with a small value and increase it until you see OOM. Monitor GPU memory with nvidia-smi or PyTorch's memory utilities. The optimal batch size also depends on sequence length; longer sequences use more memory per sample.

Throughput is not the same as latency. Batching increases latency for each individual request because the model waits for the whole batch to be ready. If your application requires low latency for each request, you may prefer a smaller batch size. If you are processing a large offline corpus, a large batch size is usually better.

Another consideration is the use of torch.no_grad(). When you are only running inference, wrapping the forward pass in torch.no_grad() disables gradient computation, which saves memory and reduces overhead. The pipeline does this automatically, but raw model calls do not.

Common Errors and How to Avoid Them

A frequent error is a device mismatch: the model is on CUDA but the input tensors are on CPU. The error message usually says something like "Expected all tensors to be on the same device." Always move the tokenized inputs to the same device as the model.

Another issue is forgetting to set padding=True when tokenizing a batch. Without padding, the tokenizer returns tensors of different lengths, and PyTorch cannot stack them into a single tensor. The tokenizer will raise an error if you try to pass a list of unequal-length lists as a tensor.

For models without a pad token, such as GPT-2, you may see an error when the tokenizer tries to pad. Set tokenizer.pad_token = tokenizer.eos_token before tokenizing. This is a common workaround.

Finally, be aware that the pipeline's device parameter expects an integer index. On a multi-GPU system, device=0 selects the first GPU. If you want to use a specific GPU, pass the index. If you are using CPU, pass device=-1 or omit the parameter.

By understanding these details, you can reliably run Python transformers GPU and batch inference in your own projects, whether you are using the pipeline for quick experiments or raw models for custom pipelines. The key is to keep the device consistent, manage padding correctly, and tune the batch size to your hardware and latency requirements.

python transformers gpu and batch inference: Practical Usage | RYUSLOG DEV