mirror of
https://github.com/Comfy-Org/comfy-kitchen.git
synced 2026-11-04 23:37:42 +00:00
[PR #277] Add FP16 tensor-core fallback for int8_linear on devices without INT8 tensor cores #280
Labels
No labels
pull-request
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
Comfy-Org/comfy-kitchen#280
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
📋 Pull Request Information
Original PR: https://github.com/Comfy-Org/comfy-kitchen/pull/277
Author: @seningpd
Created: 10/24/2026
Status: 🔄 Open
Base:
main← Head:feat/v100-int8-fp16-fallback📝 Commits (9)
6bcdd43Implement INT8 tensor core checks and fallback linear functiona0f0153Register int8_linear_fp16 in the eager backend3afffd8Expose int8_linear_fp16 at the package top level28ad72dAdd tests for FP16 tensor core INT8 fallback016a122Fix UTF-8 mojibake, pin FP16 compute on Volta, add fallback opt-out90d1977Keep FP32 activations on the INT8 path by default389d15bRestrict the FP16 fallback to devices that have FP16 tensor cores415d392Gate BF16 retention on BF16 tensor cores, not INT8 ones9894eaeStop the capability tests from leaking a simulated device📊 Changes
4 files changed (+733 additions, -3 deletions)
View changed files
📝
comfy_kitchen/__init__.py(+60 -2)📝
comfy_kitchen/backends/eager/__init__.py(+13 -0)📝
comfy_kitchen/backends/eager/quantization.py(+309 -1)➕
tests/test_int8_fp16_fallback.py(+351 -0)📄 Description
Summary
On NVIDIA Volta (sm_70, e.g. V100)
int8_linearis slower than an FP16 GEMM, because Volta has no INT8 tensor core:int8_linearrequiresmin_compute_capability=(7, 5)and_CUBLASLT_AVAILABLE, so it never registers on sm_70.int8_linearrequiresmin_compute_capability=(8, 0).torch._int_mm— a DP4A-emulated INT8 GEMM. Measured on a V100: 4096³ in 2.909 ms, versus 1.507 ms for the same FP16 matmul on the FP16 tensor cores.So the INT8 weights cost memory and buy nothing in speed, and the fallback is silent — there is no warning that a 2x-slower path was selected.
This PR adds an FP16-tensor-core fallback for devices without INT8 tensor cores. The INT8 weights stay in memory (dequantized to FP16 on the fly, exactly like the existing FP8 path), so the memory footprint is unchanged.
The key observation
For a ConvRot weight the stored matrix is
W_rot = W @ H^Tand the activation is rotated online (x_rot = x @ H), so the GEMM runs entirely in the rotated basis:That means the dequantized weight must not be rotated back. Dequantizing
W_rotand multiplying byx_rotis mathematically equivalent to the original basis, and skipping the inverse Hadamard saves one transform per layer.The activation is still quantized to INT8 with the same grid as the INT8 path, so the numerics stay on the INT8 quantization grid and only the GEMM changes.
Measured results
Real ComfyUI model load (
rayQwen21_turboV13Int8convrot.safetensors, Qwen-Image-2.1), 225 quantized layers, M=4096 tokens, Tesla V100-SXM2-16GB:Accuracy against a float32 reference (same layer, same weights):
The two paths sit on the same quantization grid, so the distance to the float32 reference is unchanged — only the GEMM precision differs (int32 accumulate vs FP16 accumulate).
Changes
comfy_kitchen/backends/eager/quantization.pyint8_linear_fp16(): INT8 linear that dequantizes to FP16 on the fly and runs the GEMM on the FP16 tensor cores_cuda_device_has_int8_tensor_core()helper, cached per device (mirrors the existing_cuda_device_is_turing()pattern)int8_linear()now delegates toint8_linear_fp16()on CUDA devices without INT8 tensor cores. Devices with INT8 tensor cores keep the existing path untouched.comfy_kitchen/backends/eager/__init__.py: registerint8_linear_fp16as an eager capabilitycomfy_kitchen/__init__.py: publiccomfy_kitchen.int8_linear_fp16()entry point, plus acomfy_kitchen::int8_linear_fp16custom op fortorch.compilecompatibilitytests/test_int8_fp16_fallback.py: 10 tests covering numerics vs the INT8 path, numerics vs a float32 reference, bias/residual, scalar weight scale, shape validation, capability registration, and the delegation switchPurely additive (460 insertions, 0 deletions).
Notes for the reviewer
int8_linear's numerical contract intact. A variant that keeps the activation in FP16 is ~25% faster again (225 layers: 1134 ms -> 859 ms, error vs float32 0.0017), but it changes the quantization grid, so it seemed better kept out of this PR. Happy to split it out if that is preferred.quantize_int8_rowwisein the CUDA backend requires sm_80+ and raisesno kernel image is available for execution on the deviceon sm_70 rather than falling back gracefully. That is a separate issue (reported separately), but it is worth a graceful fallback there too.Test plan
tests/test_int8_fp16_fallback.py: 10 passedtests/test_int8.py,tests/test_int8_input_act.py,tests/test_int8_residual.py,tests/test_int8_attention.py: 136 passed, 1154 skipped (skips are the compiled CUDA extension, unavailable in this environment)Hardware used for the measurements: Tesla V100-SXM2-16GB, torch 2.12.0+cu126, CUDA 12.8.
🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.