Running Idol Single-Image-to-3D on an M1 Max Without CUDA: 7 Minutes per Generation
Contents

In late October, Speridlabs released Idol, an image-to-3D generation model.
Feeding it a single image outputs a 3D Gaussian Splat that reconstructs occluded areas not visible in the input.
A 3D Gaussian Splat (original paper) represents 3D volume by placing hundreds of thousands of tiny ellipsoids (Gaussians) with color and opacity in 3D space, rendering them as a dense collection of points rather than polygonal surface meshes.
The installation instructions in the README state: “an NVIDIA GPU with CUDA”.
My test machine is an M1 Max 64GB MacBook Pro, which does not have CUDA.
Because PyTorch supports running on Apple Silicon GPUs via MPS (Metal Performance Shaders), it can perform tensor computation on Mac GPUs without CUDA.
I tested whether Idol would run without CUDA on Apple Silicon, and if so, whether it could turn a full-body standing illustration of my character Kana into a 3D model.
Basic Specifications: Idol
A summary of the model card and SDK README:
| Item | Details |
|---|---|
| Repository | speridlabs/idol (Apache-2.0). SDK code at speridlabs/idol-sdk |
| Input | Single image. Background removal and cropping are handled inside the pipeline |
| Output | Standard 3DGS .ply file. Gaussian count ranges from 32,768 to 1,048,576 (default: 262,144) |
| Architecture | DiT (denoising backbone) 370.6M, Octree decoder 50.8M, Gaussian decoder 237.3M, BiRefNet (background removal) 220.7M, DINOv3 ViT-H+/16 840.6M, FLUX.2 VAE 84.1M. Total: ~1.80B |
| Memory | Weights: 3.7–4.4 GB, peak: 7.2 GB (values under CUDA) |
| Official Speed | Request time for 50 steps / 262,144 Gaussians (median across 8 images): H100 6.7s, RTX 4090 19.1s, RTX 3080 46.2s |
| Environment | Python 3.10+, PyTorch 2.5+, NVIDIA GPU with CUDA |
| Access | Image encoders DINOv3 and FLUX.2-dev (only VAE is used) are gated on Hugging Face and require accepting their licenses |
The output consists solely of Gaussians; polygon meshes or skeleton rigs are not produced.
Rather than standard polygonal assets that can be directly animated in tools like Blender, the resulting .ply file is formatted for display in 3D Gaussian Splatting viewers.
Verification Environment
| Item | Details |
|---|---|
| Machine | MacBook Pro (M1 Max, 64GB memory) |
| OS | macOS 27.0 |
| GPU | MPS (PyTorch backend for Apple Silicon GPU). No CUDA |
| Python | 3.12 (Homebrew), dedicated venv for Idol |
| Key Libraries | torch 2.15.0, torchvision 0.29.2, transformers 5.22.1, diffusers 0.42.1, tensordict 0.15.4 |
| Idol SDK | GitHub commit 4702a7f (October 29, 2026) |
| Visualization | Loaded .ply in Chromium using three.js 0.170.0 and @mkkellogg/gaussian-splats-3d 0.4.7; exported multi-angle captures via Playwright |
Before starting, I stopped local image generation servers (ComfyUI and its queue runner) to free up memory.
Preparing to Run Without CUDA
The SDK consists of roughly 1,600 lines of pure PyTorch code.
It does not use custom CUDA extensions (custom CUDA kernels or rasterization libraries).
Attention uses flash_attn when available, falling back to PyTorch’s native SDPA otherwise.
CUDA dependencies were confined to optimizations:
torch.compile, batching CFG passes (evaluating conditional and unconditional passes in a single batch), cuDNN attention, and TF32 matrix multiplications are enabled only when the device is cuda.
On non-CUDA devices, these optimizations are simply bypassed and calculations run standard paths.
While IdolPipeline accepts a device argument, the bundled CLI tool idol.sample lacks a --device option.
I wrote a brief script to invoke the pipeline directly:
from idol import IdolPipeline
pipe = IdolPipeline.from_pretrained("weights/idol", device="mps")
out = pipe("kana_full.png", mode="synthetic", seed=1, steps=50, num_gaussians=262144)
out.gaussian.save_ply("kana.ply")
Running pip install -e ./idol-sdk succeeded without modification because it does not declare CUDA-only dependencies.
I set PYTORCH_ENABLE_MPS_FALLBACK=0 to ensure execution halts on any unsupported operations rather than silently falling back to CPU.
Substituting the FLUX.2-dev VAE
Idol relies on the FLUX.2-dev VAE for image feature extraction.
My Hugging Face account had not accepted the FLUX.2-dev license agreement, resulting in a 403 Forbidden error.
However, Black Forest Labs provides a vae/ directory inside FLUX.2-klein-4B, which was accessible without restriction.
The config.json inside klein’s VAE lists "_name_or_path": "black-forest-labs/FLUX.2-dev".
Its file size is 168,120,878 bytes compared to dev’s 336,213,556 bytes—exactly half because klein is stored in bf16 rather than FP32.
Since Idol casts the dev VAE to bf16 upon loading anyway, I updated flux2_vae in Idol’s config.json to point to klein.
To verify interchangeability, I compared klein against the FP32 FLUX.2-dev VAE published by Comfy-Org (Comfy-Org/flux2-dev under split_files/vae/flux2-vae.safetensors, 336,213,556 bytes).
All 251 tensors matched in name and shape.
When casting the Comfy-Org weights to bf16, all 251 tensors matched klein bit-for-bit.
Because Idol loads the VAE in bf16, loading dev versus klein feeds identical numerical weights into the model.
While Comfy-Org’s SHA-256 did not match the official dev hash reported by the Hugging Face API, the file size matches, so this is likely a difference in header metadata.
Running the Official Demo Image
I first tested the bundled demo image fig-knight.webp (a figurine of an armored knight) under default settings: mode="synthetic", seed 42, 50 steps, 262,144 Gaussians.
The run completed without errors, encountering zero unsupported MPS operations.
| Metric | M1 Max (MPS) |
|---|---|
| Model Loading | 59.8s (initial run, including image encoder download) |
| Generation | 434.1s (sampling 50 steps took 7m 00s; 8.4s per step) |
| Peak Memory | 14.79 GB (maximum torch.mps.driver_allocated_memory()) |
| Output | .ply 17.8 MB |
This was roughly 23x slower than the RTX 4090’s 19.1s.
Note that official figures represent median request times across 8 images, whereas this measures the generation phase for a single image.
Additionally, because non-CUDA backends do not batch CFG (which evaluates both conditional and unconditional passes), 50 steps require 100 sequential model evaluations.
Peak memory was roughly double the model card’s 7.2 GB figure.
BiRefNet half-precision casting also executes only under CUDA, leaving it in FP32 on MPS.
I submitted the same image with identical settings to the official Hugging Face Space (ZeroGPU).
The API reported hardware as zero-a10g.
The Space uses the original FLUX.2-dev VAE with full CUDA acceleration.
In the comparison images below, the leftmost panel shows the preprocessed input, followed by front, front-three-quarter, side, rear-three-quarter, back, and opposite side views.
All widescreen figures in this article follow this sequence.
| Environment | Result |
|---|---|
| M1 Max (MPS) | Reconstructed front and back shapes cleanly. An extra floating shield appeared detached in the air next to the knight |
| Hugging Face Space (CUDA) | Single shield held in hand; no floating duplicate |
The back of the helmet, plume, and back armor were filled in cleanly in both environments despite not appearing in the input image.
However, PyTorch generates different pseudorandom number streams between CUDA and MPS, producing different initial noise distributions even with seed 42.
Consequently, this comparison alone cannot isolate whether the duplicate shield stemmed from MPS numerical differences or random noise.
Testing a Standing Illustration of Kana
Next, I tested an existing full-body standing illustration of my character Kana, originally generated locally.
It is an 832x1216 image on a white background, showing her standing upright with arms down in a school uniform.
Settings matched the knight run (seed 42).
Generation completed in 427.7s, but the output did not resemble a human figure.
From every viewing angle, only a few dark splotches hovered near the top.
Analyzing the .ply revealed that out of 262,144 Gaussians, only 28 had opacity exceeding 0.5.
By comparison, the knight run had 50,607 on MPS and 45,701 on Space above 0.5.
Nearly every Gaussian was transparent, rendering an empty void.
The preprocessed input image showed Kana cleanly cropped on a black background.
The failure occurred in the generative phase rather than background segmentation.
Idol also predicts camera distance and elevation (pitch) from the input image.
For Kana seed 42, predicted distance was 1000 (effectively an orthographic projection from extreme distance) and pitch was 0.45 radians, markedly different from the knight’s distance of 3.09 and pitch of 0.01 radians.
This pointed to two possible explanations:
Either MPS numerical behavior diverged significantly from CUDA, causing both the knight’s shield and this collapse, or a tall, flat 2D anime illustration fell outside the model’s expected input distribution.
To check this without uploading private character assets to external spaces, I compared CPU and MPS outputs component by component under identical inputs.
Checking MPS Numerical Drift
CPU Baseline Bottleneck
Running pure CPU inference directly stalled on a single core for over 30 minutes without progressing.
Profiling via macOS sample showed execution stuck in CPU bf16 matrix multiplication.
SDK inspection showed the FLUX.2 VAE is loaded in bf16 and inputs are hardcoded to images.to(torch.bfloat16).
The DiT and decoders also specify self.dtype = torch.float16, casting weights to half-precision.
Because CPU half-precision math is painfully slow, I built an all-FP32 CPU pipeline to serve as the ground-truth baseline.
Additionally, the VAE samples latent distributions using random numbers via latent_dist.sample().
To prevent device RNG divergence from altering latent inputs, I patched it to return the distribution mode (mode()) during comparisons.
Component-by-Component Comparison
Comparing MPS default (mixed FP16/bf16) and MPS full-FP32 against the CPU full-FP32 baseline, using identical preprocessed inputs and identical initial DiT noise vectors.
Relative error is calculated as the L2 norm of the difference divided by baseline norm.
| Component | MPS Default (FP16/bf16) | MPS All-FP32 |
|---|---|---|
| DINOv3 Features | 1.02% | 0.00016% |
| FLUX.2 VAE Features | 0.84% | 0.0029% |
| DiT Latent Shape t=1000 | 0.11% | 0.00006% |
| DiT Latent Shape t=500 | 0.33% | 0.00038% |
| DiT Latent Shape t=50 | 0.19% | 0.00021% |
| DiT Camera t=1000 / 500 / 50 | 0.09% / 0.15% / 0.26% | < 0.0001% |
In diffusion pipelines, denotes the denoising timestep ( represents pure noise, decreasing toward for the final reconstructed shape).
No NaNs or Infs appeared under any condition.
In full FP32, MPS matched CPU output across six decimal places.
MPS operations are mathematically sound.
The 0.1–1% drift observed in default mode falls within standard half-precision rounding bounds.
This confirms MPS arithmetic behaves consistently with CPU execution.
Because the failure was not caused by broken MPS kernels, I turned to testing alternative seeds and inputs.
Varying Random Seeds
Using the same standing illustration and default settings, I ran two additional seeds.
| Seed | Generation | Opacity > 0.5 | Predicted Pitch | Result |
|---|---|---|---|---|
| 42 | 427.7s | 28 | 0.45 rad | Collapsed (empty splotches) |
| 1 | 409.1s | 199,600 | 0.08 rad | Full body formed |
| 2 | 419.4s | 214,620 | 0.20 rad | Body formed, but side ponytail detached and floated |
Seed 1 produced a standing pose matching the front input view.
Rotating the model revealed a continuous 3D figure, synthesizing the back shirt, skirt pleats, knee socks, and shoes.
Seed 2 reconstructed the torso, uniform, and legs, but detached the side ponytail and scrunchie, leaving them suspended behind the head.
From the front, the ponytail was occluded by the skull; rotation exposed the spatial gap.
Similar to the knight’s duplicated shield, slender protruding features suffered depth misplacement.
Only seed 1 yielded a coherent 3D shape.
The collapsed seed 42 was the sole run with an outlier predicted pitch, suggesting camera misestimation may trigger structural failure.
Zooming into Seed 1
Close-up render of seed 1 around the head (leftmost panel is the input):
The side ponytail, blue scrunchie, and ahoge remained anchored to the head in 3D.
Hair on the back of the head, unrepresented in the input, filled in as shoulder-length volume.
However, facial line art and eye highlights softened considerably compared to the 2D source, and yellow mottled artifacts appeared across the opposite side of the hair.
Testing with Chibi Art
SDK demo images (fig-*.webp) consistently depict chibi/proportioned figures with large heads.
If training data heavily favored stylized proportions, chibi inputs might perform better.
I tested a front-facing chibi illustration of Kana (832x1024, white background, red uniform skirt).
| Seed | Generation | Opacity > 0.5 | Predicted Pitch | Result |
|---|---|---|---|---|
| 42 | 414.3s | 163,287 | 0.19 rad | Full body formed. Back of skirt turned white |
| 1 | 415.0s | 197,486 | 0.16 rad | Full body formed. Skirt remained red on reverse |
Seed 42, which collapsed on the full-body standing art, successfully formed a complete body with chibi proportions.
In both seeds, the side ponytail and scrunchie stayed properly attached to the head.
In seed 42, viewing the rear revealed that the back half of the red skirt bleached white, appearing as an elongated shirt hem.
Seed 1 preserved red skirt coloring on the reverse, though heavy dark contour strokes appeared along hair flow on the back of the head—mimicking input line-art aesthetics onto unobserved surfaces.
Testing with Figure-Rendered Artwork
Reviewing the 20 character demo images (fig-*.webp) showed they are 3D CG renders depicting soft vinyl or PVC figurines with studio lighting.
Even nb-dragon-anime.webp, titled with “anime”, depicted a sculpted figurine rather than flat cell illustration.
| Official Demo Images | Kana Standing Art | |
|---|---|---|
| Style | Figurine 3D CG with shading, specular highlights, ground contact shadows | Flat-color cell animation art with black outlines and minimal shading |
| View Angle | Primarily slightly elevated 3/4 views | Direct front-facing |
| Proportions | 2–3 heads tall | ~7 heads tall (chibi is ~3 heads tall) |
Generation quality ranked knight > chibi > standing art, mirroring similarity to demo images.
Using the standing and chibi illustrations as references, I had Codex generate figure-style re-renderings.
Using the knight image as a stylistic reference, prompts specified black backgrounds, pronounced shading and highlights, and slightly elevated three-quarter perspectives.
I generated four variants (1254x1254 each): two scale figurines with clear acrylic circular bases (Full-body A1, A2) and two chibi soft-vinyl figures (Chibi B1, B2).
Hair and outfit designs remained faithful to character references.
| Input | Seed | Generation | Opacity > 0.5 | Predicted Pitch | Result |
|---|---|---|---|---|---|
| Full-body A1 | 42 | 400.8s | 203,056 | 0.23 rad | Full body formed. Base shadow persisted as dark floor artifact |
| Full-body A1 | 1 | 404.3s | 203,693 | 0.11 rad | Full body formed cleanly |
| Full-body A2 | 42 | 400.6s | 172,740 | 0.10 rad | Full body formed cleanly |
| Chibi B1 | 42 | 402.0s | 243,908 | 0.17 rad | Full body formed cleanly |
| Chibi B2 | 42 | 400.7s | 239,480 | 0.16 rad | Full body formed cleanly |
Seed 42, which collapsed on flat-color art, successfully generated complete bodies under figure styling.
All five runs produced intact torsos and separated legs.
Front and front-three-quarter angles mirrored the input shading and specular highlights accurately.
Volume depth measured 0.73–0.74x the frontal width, noticeably thicker than flat standing art seed 1 (0.57x).
Only A1 seed 42 retained base shadow artifacts as a dark ring and hazy floor patch.
However, the side ponytail duplicated into twin tails across all five runs.
Despite the input having a single ponytail on Kana’s left side, rotating to the rear revealed an identical ponytail and blue scrunchie mirrored on the right side.
The image below shows Full-body A1 seed 1 (rear-three-quarter and back) on the left, and Chibi B1 seed 42 (rear-three-quarter and back) on the right:
Flat standing and chibi inputs maintained a single ponytail when viewed from the back.
Figure-style rendering stabilized overall anatomy, but triggered bilateral hair mirroring on unobserved angles.
Idol accepts only a single image with no provision for multi-view conditioning.
IdolPipeline consumes one image, and DiT conditions generation solely on features from that view.
Passing a directory to --image simply processes images independently in sequence.
All unobserved geometry relies strictly on generative priors.
View Angles and Asymmetric Accessories
To determine whether bilateral duplication was specific to side ponytails or affected any asymmetric accessory, I generated two additional variations based on Chibi B1:
C1 and C2 depicted the same chibi figure rotated toward her left (the ponytail side) at roughly 45° and 60° three-quarter angles, exposing the ponytail protruding laterally and leaving the opposite temple bare.
D1 removed the ponytail, styling the hair down with a single red star hairpin on her left side.
| Input | Seed | Generation | Opacity > 0.5 | Result |
|---|---|---|---|---|
| C1 (~45°) | 42 | 400.8s | 243,362 | Duplicated to 2 tails in back. Mirrored tail lacked scrunchie |
| C1 (~45°) | 1 | 401.6s | 240,408 | Duplicated to 2 tails in back, with 2 scrunchies |
| C2 (~60°) | 42 | 402.3s | 231,790 | Single ponytail preserved. Ahoge detached and blurred |
| D1 (Star hairpin) | 42 | 400.7s | 245,313 | Hairpin remained singular on left side |
| D1 (Star hairpin) | 1 | 403.9s | 240,698 | Hairpin remained singular on left side |
Close-up comparisons of head regions (top: C1 seed 1, middle: C2 seed 42, bottom: D1 seed 42):
The asymmetric star hairpin never duplicated across either seed.
Back hair formed a unified surface, with only the clip edge visible along the left contour.
Bilateral mirroring specifically affected hair volume projecting outward from the head rather than surface accessories.
At ~45°, the ponytail still duplicated.
Rotating to ~60°, where ponytail depth was unambiguous, preserved a single tail.
However, C2 caused the ahoge to detach and blur into a hazy clump, likely because extreme profile perspective caused depth ambiguity across the thin antenna hair.
Near-frontal single views cannot resolve whether an asymmetric hair cluster is unilateral or bilateral, causing the prior to bias toward twin tails.
Across 13 generations featuring Kana’s original hairstyle (flat and figure styles combined), 12 produced geometry (excluding 1 collapse), but none reconstructed the back of the head cleanly.
Failures varied across detached floating ponytails, bilateral twin tails, floating ahoge, and mottled back-hair streaks.
While shaded inputs stabilized torso and limb geometry, resolving the back of the head from a single near-frontal 2D image remained unresolved.
Gaussian Counts and Denoising Steps
To evaluate latency versus quality, I adjusted Gaussian counts and step counts using standing art seed 1.
Gaussian Count
| Count | Generation | Peak Memory | .ply Size |
|---|---|---|---|
| 262,144 (Default) | 409.1s | 15.61 GB | 17.8 MB |
| 1,048,576 (Max) | 427.3s | 15.61 GB | 71.3 MB |
A 4x increase in Gaussian count extended generation time by only 18 seconds and left peak memory unchanged.
DiT sampling consumes virtually all time and memory; Gaussian count impacts only the trailing decoder stages.
Because random seed was identical, overall geometry matched.
Left to right: 262,144 front, 1,048,576 front, 262,144 front-3/4, 1,048,576 front-3/4.
At 1,048,576 Gaussians, hair strand borders and collar seams gained sharpness.
Eye softening remained unaffected, and an isolated white point artifact hovered before the nose in both renders.
Step Count
| Steps | Generation | Opacity > 0.5 | Result |
|---|---|---|---|
| 50 (Default) | 409.1s | 199,600 | Full body formed |
| 25 | 206.9s | 167,932 | Face omitted; entire figure blurred into a blob |
Halving steps halved generation time, but erased facial features, fused the legs into a single mass, and reduced the skirt to gray haze.
The README notes “Fewer are faster but lose some detail”, but on this input, reduction destroyed structural integrity.
Predicted pitch was 0.03 rad (accurate), indicating the collapse stemmed from incomplete noise removal rather than camera error.
On an M1 Max, runtime is dominated by the 50 DiT steps; reducing Gaussian count offers negligible speedup.