deepspeedai / deepspeedai/DeepSpeed
`_cast_module_mixed_precision` casts every dtype torch calls floating point, corrupting FP8/MX/NVFP4 parameters
@sfc-gh-abkulkarni is already working on this.
Since Sep 6, 2026.
- Dominant language
- Python
- Stars
- 43.1k
- Forks
- 5k
- Avg merge
- 4d 15h
- Merged PRs (30d)
- 112
Description
Repo: deepspeedai/DeepSpeed
Version: DeepSpeed 0.19.2, PyTorch 2.11.0+cu130
Problem
DeepSpeedEngine._cast_module_mixed_precision (deepspeed/runtime/engine.py:1608) casts every
parameter for which p.is_floating_point() is true:
for p in self.module.parameters(recurse=True):
if p.is_floating_point() and p.dtype != param_dtype:
p.data = p.data.to(param_dtype)
is_floating_point() is true for the narrow-precision storage dtypes as well, so quantized
parameters that are frozen, never receive gradients, and are consumed directly by a
quantization-aware GEMM are swept into the mixed-precision cast. On PyTorch 2.11:
| dtype | p.data.to(torch.bfloat16) |
|---|---|
float8_e4m3fn, float8_e5m2 |
silently casts; 2x memory, quantized representation lost |
float8_e4m3fnuz, float8_e5m2fnuz |
silently casts (ROCm variants) |
float8_e8m0fnu |
silently casts; this is the MX block-scale dtype, exponent-only |
float4_e2m1fn_x2 |
raises NotImplementedError: "copy_" not implemented for 'Float4_e2m1fn_x2' |
Three distinct failure modes:
- FP8 weights double from one byte per element to two.
- MX scales (
float8_e8m0fnu) hold a power-of-two exponent, not a value. Casting to bf16 and
back does not round-trip the encoding. - NVFP4 (
float4_e2m1fn_x2) packs two 4-bit values per byte and has nocopy_implementation,
sodeepspeed.initializefails outright on any model holding such a parameter.
This matters for the increasingly common setup of training LoRA adapters on top of a checkpoint
held in its native quantized form.
Reproduction
import torch, deepspeed, torch.nn as nn
DTYPE = torch.float8_e4m3fn # or float8_e8m0fnu, or float4_e2m1fn_x2 to see the crash
class M(nn.Module):
def __init__(self):
super().__init__()
self.frozen = nn.Parameter(torch.zeros(4096, 4096, dtype=DTYPE), requires_grad=False)
self.adapter = nn.Linear(4096, 16, bias=False) # the only trainable tensor
m = M()
print("before:", m.frozen.dtype, m.frozen.numel() * m.frozen.element_size() // 2**20, "MiB")
engine, *_ = deepspeed.initialize(
model=m,
model_parameters=[p for p in m.parameters() if p.requires_grad],
config={
"train_batch_size": 1,
"bf16": {"enabled": True},
"zero_optimization": {"stage": 2},
},
)
p = engine.module.frozen
print("after: ", p.dtype, p.numel() * p.element_size() // 2**20, "MiB")
Observed with float8_e4m3fn: 16 MiB before, bfloat16, 32 MiB after.
Observed with float4_e2m1fn_x2: NotImplementedError during initialize.
Expected in both cases: the parameter is left untouched.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Assessment
This issue has not been assessed yet.