Skip to content

Default to fp32 on CPU/MPS instead of bf16 (fixes #15) - #20

Open
hemanth1999k wants to merge 1 commit into
MisoLabsAI:mainfrom
hemanth1999k:fix/cpu-dtype-default
Open

Default to fp32 on CPU/MPS instead of bf16 (fixes #15)#20
hemanth1999k wants to merge 1 commit into
MisoLabsAI:mainfrom
hemanth1999k:fix/cpu-dtype-default

Conversation

@hemanth1999k

Copy link
Copy Markdown

Problem

load_miso_8b defaults to torch.bfloat16 and _load_model applies it unconditionally:

def load_miso_8b(device="cuda", model_path_or_repo_id=None, dtype=torch.bfloat16): ...
# _load_model:
model.to(device=device, dtype=dtype)

The checkpoint weights are stored as fp32, so on CPU (and MPS) the default silently downcasts to bf16. bf16 buys no speed/memory benefit on those devices, and because audio generation is autoregressive, small bf16/fp32 rounding differences alter the next-frame state and compound over time — CPU bf16 and CPU fp32 produce audibly different output, especially for longer generations. (See #15 for a codebook-level repro.)

Fix

Resolve the default dtype from the target device instead of hardcoding bf16:

  • CUDAtorch.bfloat16 (unchanged behavior)
  • CPU / MPStorch.float32

dtype now defaults to None and is resolved via a small _default_dtype_for_device helper. An explicitly passed dtype is still honored, so callers who deliberately want bf16 on CPU can opt in — this is fully backward compatible for CUDA users.

Notes

  • One-file change, no new dependencies.
  • I don't have a CUDA box to run the full model end-to-end; the dtype-resolution logic itself is covered by a quick local check (cuda/cuda:0 → bf16, cpu/mps → fp32). Happy to adjust naming or fold this into a broader device-handling refactor if you'd prefer.

Fixes #15.

Checkpoint weights are fp32, but load_miso_8b applied torch.bfloat16
unconditionally regardless of device. On CPU/MPS bf16 brings no
speed/memory benefit, and because generation is autoregressive the
bf16/fp32 rounding differences compound frame-over-frame into audibly
divergent output (issue MisoLabsAI#15).

Resolve the default dtype from the device: bf16 on CUDA, fp32 elsewhere.
An explicitly passed dtype is still honored, so this is backward
compatible for callers that opt into bf16.
@robbiemu

robbiemu commented Jun 4, 2026

Copy link
Copy Markdown

❤️
Not sure that the reasoning is correct here but the solution might in fact be that they migrate all devices to fp32, if they really testing in full fp32 instead of shortcutting to nvidia/cuda bf16.

I just finished a test on vast.ai (which I had to use because I don't have Nvidia hardware) and I can confirm CUDA's BF16 is identical to CPU bf16 on the edge case I could identify.

Comment thread generator.py
def _default_dtype_for_device(device: str) -> torch.dtype:
"""Pick a sane default dtype for the target device.

Checkpoint weights are stored as fp32. bf16 is a worthwhile speed/memory

@robbiemu robbiemu Jun 5, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Apple Silicon started supporting bfloat16 (BF16) at the hardware level with the M2 family of chips. I believe that statement is true only my homelab M1's (line 201, "no such upside")

@robbiemu

robbiemu commented Jun 5, 2026

Copy link
Copy Markdown

I can also add this in support of this pr (or at least the approach it embodies):

My tests show a real numeric difference between CPU and MPF fp32 logits, but it is small fp32 backend drift. It is not a decision-level CPU/MPS fp32 disagreement.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

CPU bf16 default causes divergent audio vs fp32 generation

2 participants