NEWVectors or files. Pick a path.Start →
    Search & Discovery
    8 min read
    Updated 2026-10-13

    Why Is My Image Embedding Model So Slow on CPU?

    An image embedding model that crawls on CPU is usually running in bfloat16 on a processor with no hardware for it, or leaving most of its cores idle. PyTorch autocast on CPU defaults to bfloat16, and without AVX512-BF16 or AMX the math runs in software: we measured 13.0 s per image under autocast against 0.555 s in fp32 for SigLIP base. Run CPU inference in fp32, give each process one thread, and start one model copy per core.

    Embeddings
    CPU Inference
    PyTorch
    Performance
    Images
    Ray

    Why is my image embedding model so slow on CPU?



    Usually one of two things. The model is running in bfloat16 on a processor that has no hardware for bfloat16, so every matrix multiply is emulated in software, or the job is using one core while the rest sit idle. PyTorch's autocast picks bfloat16 on CPU by default, and most cloud CPUs without AVX512-BF16 or AMX pay heavily for it. On our own production batch, each image took about 20 seconds and the batch wrote about 185 images an hour. Running in fp32 with one model copy per core brought the same workload to about 10,250 images an hour.

    The fix is three settings: run CPU inference in fp32, give each process the number of threads it has cores for, and run several single-threaded copies of the model, one per core.

    How much slower is bfloat16 on a CPU without bfloat16 hardware?



    About 23 times slower, in our measurement. We timed the SigLIP base vision encoder (google/siglip-base-patch16-224) on 16 images of 1024 by 1024 pixels on a Google Cloud node with an AMD EPYC 7B12, which has AVX2 and no AVX512.

    SettingSeconds per image
    ------:
    fp32, 1 thread0.555
    fp32, 2 threads0.289
    fp32, 4 threads0.256
    autocast bfloat16, 1 thread13.016
    autocast bfloat16, 2 threads13.052
    Measured by Mixpeek on 2026-10-12. Adding a second thread halved the fp32 time and did nothing for bfloat16, which is what emulated arithmetic looks like: the time goes into converting numbers, which more threads do not help.

    Why does PyTorch use bfloat16 on CPU?



    Because that is autocast's default for the CPU device. The PyTorch automatic mixed precision documentation says autocast uses torch.float16 for CUDA and torch.bfloat16 for CPU when no dtype is given, and lists linear, matmul, conv2d and bmm among the CPU ops that autocast to bfloat16. Those are most of an image encoder's work.

    Code written for a GPU often wraps inference in torch.autocast to save memory and time there. Moved to a CPU worker, the same line keeps running and quietly switches the model to bfloat16. Nothing errors and the embeddings come out about the same, so the only symptom is speed.

    How do I check whether my CPU supports bfloat16?



    On Linux, look for the instruction set flags:
    grep -o -w -E 'avx512_bf16|amx_bf16' /proc/cpuinfo | sort -u
    No output means no bfloat16 hardware, and you should run in fp32. Intel Xeon processors from Cooper Lake onward and AMD EPYC processors from Zen 4 onward report avx512_bf16, and Intel Sapphire Rapids and later also report amx_bf16. Cloud instance families mix processor generations, so check on the machine the job actually runs on. Google Cloud lists the processors behind each machine series on its CPU platforms page.

    How do I run an image embedding model fast on CPU?



    Turn autocast off for the CPU path, run without gradients, and set the thread count yourself:
    import torch
    from transformers import SiglipImageProcessor, SiglipVisionModel
    
    torch.set_num_threads(1)  # one thread per process; run one process per core
    
    model_id = "google/siglip-base-patch16-224"
    processor = SiglipImageProcessor.from_pretrained(model_id)
    model = SiglipVisionModel.from_pretrained(model_id, torch_dtype=torch.float32).eval()
    
    def embed(images):
        pixels = processor(images=images, return_tensors="pt").pixel_values
        with torch.inference_mode():  # fp32, no autocast on CPU
            return model(pixel_values=pixels).pooler_output
    Keep autocast for the GPU path, where it helps. A simple guard is to enter torch.autocast only when the device is CUDA.

    Why does my embedding job use one core when the machine has eight?



    Two common causes. The thread count is wrong: PyTorch sets its own thread pool size (torch.set_num_threads), and several processes each using every core fight over the same cores. Or the pipeline never starts more than one copy of the model.

    Threads stop paying off quickly for a small encoder. In the table above, going from 2 threads to 4 saved 11%. For throughput across many images, several single-threaded copies of the model beat one copy with many threads, as long as memory holds them.

    The second cause hit our own pipeline. Our image batches run on Ray Data, and the embedding step was an actor pool that started at one actor with room to grow to eight. Ray Data split the job's 4 CPUs across the pipeline's five steps, and the embedding step's share sat at 0.2 to 0.3 CPU, under the 1 CPU a second actor needed. The pool never grew. One worker ran at about 1.1 cores and the other at about 0.1.

    We fixed it with a pool whose minimum equals its maximum, so every copy starts at once: one single-threaded actor per worker CPU, each reserving 0.75 CPU and leaving the rest for decoding, thumbnails and writes. In Ray this is min_size and max_size on ActorPoolStrategy.

    What changed when we fixed both?



    Here is the same pipeline on a GKE node with an Intel Xeon at 2.20 GHz, two workers of 2 CPUs each, embedding 3,414 images at the sizes of a real product catalog.

    VersionModel copiesSeconds per image in a copyImages an hourAll 3,414 images
    ---------:---:---
    Before1 copy, 1 CPU, batches of 6419.7 to 21.0180about 19 hours
    More copies only, still bfloat164 copies, 0.75 CPU each, batches of 1620.0720about 4.7 hours
    More copies and fp324 copies, 0.75 CPU each, batches of 161.32 median10,25320.0 minutes
    Measured by Mixpeek on 2026-10-12. Starting more copies multiplied throughput by 4, and fp32 multiplied it by about 14 on top of that.

    Batch size mattered for memory. With 64 large images decoded at once, the single copy held 6.8 GB. At 16 per batch, two copies and their share of the other steps peaked at 5.5 GB per worker.

    Should I use a GPU instead?



    For large models or millions of images, often yes. A GPU runs the same encoder far faster per image, and autocast in float16 is the right setting there. A CPU worker is cheaper to keep running, scales to zero more easily and is enough for many catalogs: about 10,000 images an hour on 4 CPUs covers a 100,000-image catalog in roughly ten hours. Measure on your own model and images before choosing, since per-image cost on our Intel node was 2.4 times the AMD node's.

    How does Mixpeek run image embedding on CPU?



    Mixpeek reads your files where they already live, in S3, GCS or any bucket, and turns what's in them into data your software can search, classify and moderate. Its image extractor runs these settings on CPU batch workers since October 13, 2026: fp32 on CPU, autocast only on GPU, one model copy per worker CPU and batches of 16. You upload or sync a bucket and the batch embeds every image with no pipeline of your own; the change is in the changelog. Extractors are described in the feature extractor docs, and prices are on the pricing page.

    To choose a model, see the best multimodal embedding models and the best self-hosted embedding models. To estimate what a whole library costs to index, see how much it costs to make a media library searchable.

    Frequently Asked Questions



    Why is my image embedding model so slow on CPU?



    Most often it runs in bfloat16 on a CPU with no bfloat16 hardware, so the math is emulated, or it uses one core of many. Run CPU inference in fp32, set one thread per process and run one model copy per core.

    Does torch.autocast speed up CPU inference?



    It can on CPUs with bfloat16 hardware, such as Intel Xeon with AVX512-BF16 or AMX. On CPUs without it, autocast's default bfloat16 is emulated, and in our test SigLIP base took 13.0 seconds per image against 0.555 seconds in fp32.

    How many threads should PyTorch use for inference?



    For throughput over many images, one thread per model copy and one copy per core. Extra threads help a single request finish sooner, but in our test going from 2 threads to 4 saved only 11%.

    How fast can a CPU embed images?



    With SigLIP base in fp32 on our test nodes, 0.26 to 0.56 seconds per image on an AMD EPYC node with 1 to 4 threads, and about 1.3 seconds per copy on a shared Intel Xeon hyperthread. Four CPUs embedded about 10,250 catalog images an hour, including download and decoding.
    Managed Mixpeek

    Put multimodal search to work

    Connect a bucket and Mixpeek runs the whole multimodal search pipeline for you: extraction, indexing, and search over your own objects. No models to wire up, nothing to host.

    Start with Managed
    MVS · bring your own

    Already have vectors?

    Keep your embeddings on your own cloud and run dense, sparse, and BM25 search directly on object storage. From $25/mo.

    Start with MVS

    Run this on your own data

    Point Mixpeek at the storage you already have and search your video, images, audio, and documents the way this guide describes. Build starts at $25/mo for up to 1M vectors.

    Search your own archiveRead Docs

    Related guides

    Search & Discovery

    Why Does My Image Search Miss Small Objects in a Photo?

    Image search misses small objects because most systems store one vector per photo, computed from a copy shrunk to about 224 pixels. A 100-pixel object in a 4000x3000 photo becomes 5 to 8 pixels, smaller than one of the model's image patches, and CLIP's center crop drops a quarter of a wide photo before the model sees it. The fix is to embed crops of each photo as well as the whole, and rank each photo by its best-matching crop.

    Read guide →
    Search & Discovery

    What Is Composite Clustering? Clustering Across Multiple Feature Spaces (and Clusters of Clusters)

    Two things people call composite clustering: multi-feature clustering groups documents once using several embedding spaces at once (text + image + audio + faces), with concatenate / independent / weighted combine strategies; composite (cluster-of-clusters) clustering groups the centroids of prior clusterings to reveal how your groupings relate. How each works, the normalization and per-feature-weight traps, how it differs from multi-vector retrieval, and the honest limits (composite is a pattern map, not a per-document cross-tab).

    Read guide →
    Search & Discovery

    Why Does My Content Moderation Flag So Many False Positives?

    Content moderation flags too much when one confidence threshold does every job. Default cutoffs are low (Amazon Rekognition returns any moderation label at 50% confidence unless you set one), harmful content is rare so even an accurate classifier flags more safe items than real ones, and a video checked frame by frame gives every frame a chance to misfire. Set a threshold per category from your own labeled sample, send a middle band to review, and judge video by segment with context.

    Read guide →