CUDA Embedding Lookup Optimization Guide

December 25, 2025 9 min By RightNow AI Team

Introduction

Embedding lookups are memory-bound operations that index into large tables. For language models with 50K+ vocabulary, efficient embedding access is critical. The challenge is random memory access patterns that defeat caching and coalescing.

This guide covers memory layout optimization, batched lookups, and sparse gradient techniques for training.

Common Performance Issues

  • Random access pattern defeats L2 cache
  • Uncoalesced memory access for embedding dimension
  • Large embedding tables exceed GPU memory
  • Dense gradient updates waste memory for sparse access

Optimization Techniques

1. Coalesced Dimension Access

Parallelize across embedding dimension for coalesced reads.

2. Index Sorting

Sort indices to improve cache locality for repeated tokens.

3. Sparse Gradient Updates

Only update embeddings that were accessed in forward pass.

Implementation Comparison

Before (Naive Implementation)

Sequential read of embedding dimension wastes memory bandwidth.

__global__ void embedding_naive(float* table, int* indices, float* output,
                                 int num_indices, int embed_dim) {
    int idx = blockIdx.x * blockDim.x + threadIdx.x;
    if (idx >= num_indices) return;

int token_id = indices[idx];
    for (int d = 0; d < embed_dim; d++) {
        output[idx * embed_dim + d] = table[token_id * embed_dim + d];
    }
}

After (Optimized Implementation)

Parallelizing across embedding dimension enables coalesced memory access.

__global__ void embedding_coalesced(float* table, int* indices, float* output,
                                    int num_indices, int embed_dim) {
    int token_idx = blockIdx.x;
    int dim_idx = threadIdx.x;

if (token_idx >= num_indices) return;

int token_id = indices[token_idx];
    float* src = table + token_id * embed_dim;
    float* dst = output + token_idx * embed_dim;

// Coalesced read: threads read consecutive dimensions
    for (int d = dim_idx; d < embed_dim; d += blockDim.x) {
        dst[d] = src[d];
    }
}
// Launch: embedding_coalesced<<<num_indices, 256>>>(...)

Performance Results

Metric Naive Optimized Improvement
Throughput (GB/s) 120 480 4x
Latency per batch 85μs 22μs 3.9x

Frequently Asked Questions

How do I handle embeddings larger than GPU memory?

Use NVIDIA Merlin or custom implementations with: (1) CPU offloading with prefetch, (2) Distributed embedding across GPUs, (3) Hash embeddings for compression.