Supporting Block-Wise FP8 Scaled quantization models - Kohya using this better quality than Tensor-Wise Scaling
- Dominant language
- Python
- Stars
- 133k
- Forks
- 15.7k
- Avg merge
- 1d 6h
- Merged PRs (30d)
- 155
Description
# Feature Request: Support Block-wise FP8 Scaled Quantization - Currently not working throwing error
## Summary
Add support for block-wise FP8 quantization in addition to the current per-tensor FP8 implementation. Block-wise quantization provides ~2-3% better quality compared to per-tensor quantization while maintaining the same memory footprint.
## Current Situation
ComfyUI currently supports FP8 scaled models with **per-tensor quantization** (scalar `scale_weight`). This works well, but block-wise quantization offers superior quality for minimal additional complexity.
### Current Implementation
- Uses scalar `scale_weight` tensors: `torch.Size([])`
- Calls `scale_weight.squeeze()` to get scalar value
- Uses `torch._scaled_mm()` for efficient FP8 matrix multiplication
### Proposed Enhancement
- Support multi-dimensional `scale_weight` tensors: `torch.Size([out_features, num_blocks, 1])`
- Automatically detect quantization mode based on scale shape
- Fall back to dequantization when block-wise scales are present
## Why This Matters
**Quality Improvement:**
- Block-wise quantization: ~2-3% better quality than per-tensor
- Better preservation of outliers and dynamic range
- Industry standard for high-quality FP8 conversion
**Compatibility:**
- Works with models from Musubi Tuner and other FP8 conversion tools
- Backward compatible (existing per-tensor models continue to work)
- No changes needed to model files or workflow
## Technical Details
### Modified Forward Pass
The change is in `fp8_optimization.py`, function `fp8_linear_forward()`. The patch adds a check for `scale_weight.ndim`:
```python
def fp8_linear_forward(cls, base_dtype, input):
weight_dtype = cls.weight.dtype
if weight_dtype in [torch.float8_e4m3fn, torch.float8_e5m2]:
scale_weight = getattr(cls, 'scale_weight', None)
if scale_weight is None:
scale_weight = torch.ones((), device=input.device, dtype=torch.float32)
# Check if block-wise quantization (3D scale: [out, num_blocks, 1])
if scale_weight.ndim == 3:
# Block-wise quantization path
import torch.nn.functional as F
original_dtype = scale_weight.dtype
out_features, num_blocks, _ = scale_weight.shape
# Dequantize weight: reshape and multiply by scale
dequantized_weight = cls.weight.to(original_dtype).contiguous().view(out_features, num_blocks, -1)
dequantized_weight = dequantized_weight * scale_weight
dequantized_weight = dequantized_weight.view(cls.weight.shape)
# Perform linear transformation
if cls.bias is not None:
output = F.linear(input, dequantized_weight, cls.bias)
else:
output = F.linear(input, dequantized_weight)
return output
else:
# Original per-tensor path (scalar scale)
if len(input.shape) == 3:
input_shape = input.shape
scale_weight = scale_weight.to(input.device).squeeze()
scale_input = torch.ones((), device=input.device, dtype=torch.float32)
input = torch.clamp(input, min=-448, max=448, out=input)
inn = input.reshape(-1, input_shape[2]).to(torch.float8_e4m3fn).contiguous()
bias = cls.bias.to(base_dtype) if cls.bias is not None else None
o = torch._scaled_mm(inn, cls.weight.t(), out_dtype=base_dtype, bias=bias, scale_a=scale_input, scale_b=scale_weight)
return o.reshape((-1, input_shape[1], cls.weight.shape[0]))
else:
return cls.original_forward(input.to(base_dtype))
else:
return cls.original_forward(input)
```
### Key Changes
1. **Line 9-26**: New block-wise quantization path
2. **Line 28-42**: Existing per-tensor path (unchanged)
3. **Automatic detection**: Uses `scale_weight.ndim` to select the right path
## Benefits
- ✅ **Backward compatible**: Existing per-tensor models continue to work
- ✅ **Better quality**: ~2-3% improvement with block-wise quantization
- ✅ **Minimal code change**: Only modifies one function
- ✅ **Industry standard**: Aligns with best practices from Musubi Tuner, ComfyUI forks
- ✅ **No performance penalty**: Block-wise uses the same memory, similar speed
## Use Cases
- Users converting models with FP8 optimization tools
- Users wanting higher quality FP8 models
- Compatibility with Musubi Tuner and other training frameworks
- Professional workflows requiring maximum quality
## Example Model Comparison
**Per-tensor (current):**
```
scale_weight: dtype=torch.bfloat16, shape=torch.Size([]) # scalar
```
**Block-wise (proposed):**
```
scale_weight: dtype=torch.bfloat16, shape=torch.Size([3072, 48, 1]) # per-block
```
Both use the same FP8 weights (`torch.float8_e4m3fn`), only the scale granularity differs.
## Testing
I can provide test models in both formats to verify the implementation if needed.
---
Thank you for considering this enhancement! Block-wise FP8 quantization would significantly improve quality for users working with FP8 models.
Contributor guide
Research direction
Start by reading fp8_optimization.py and the fp8_linear_forward() entry point, then compare the existing scalar scale_weight path with the proposed multi-dimensional scale handling. Done means block-wise FP8 models from Musubi Tuner and existing per-tensor models both work without the reported error, while preserving the current behavior for scalar scales.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- python, pytorch
- Domain
- machine-learning, performance
- Issue type
- Feature
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 48/100