feat: add FP8 blockwise weight quantization and FP8(E4M3) KV cache support - #586
Open
shsaihdsaiudh wants to merge 4 commits into
Open
shsaihdsaiudh wants to merge 4 commits into
shsaihdsaiudh wants to merge 4 commits into
Conversation
Weight path: - add the fp8_blockwise quantization scheme and wire it into Linear layer construction and FP8 checkpoint loading (weight_scale_inv) - decode GEMM routing: fused fp8_blockwise_gemm kernel for small M, tensor-core mma path up to M=32, dequantize+GEMM fallback otherwise - opt-in Marlin fused GEMM path, with the rounding carry in F16 scale conversion KV cache path: - FP8(E4M3) KV cache quantization for the paged attention backend with per-token-per-kv-head dynamic scales - expose kv_cache_dtype=fp8 through EngineConfig, LLM and the server entry points; reject FP8 KV cache on non-paged attention backends Requires the InfiniCore fp8_blockwise_gemm / fp8_blockwise_dequantize operators and FP8 paged attention support.
dependencies was empty, so a fresh environment could not import infinilm.llm (janus, xxhash missing). Core deps cover the LLM path; fastapi/uvicorn/httpx/pydantic/msgspec/pyzmq/psutil move to the server extra and pillow to multimodal.
The top-level thread_loop catch always logged "fatal exception" even when the worker had already been asked to stop (teardown racing an in-flight CUDA call), which made normal shutdowns indistinguishable from real faults in the logs. Expected deaths are now warnings; genuine failures remain error-level and still propagate to waiters via should_exit_.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
FP8Blockwisequantization scheme (csrc/layers/quantization/fp8_blockwise.{cpp,hpp},QuantScheme::FP8_W8A16): loads FP8 E4M3 checkpoints with 128x128 block scales (quant_method: "fp8",weight_block_size: [128,128]— Qwen3-FP8 series, DeepSeek-style) and runs them without materializing BF16 weights.FP8Blockwise::forward:M <= 32dispatches to InfiniCore's fusedfp8_blockwise_gemm(SIMT forM <= 8, tensor-core mma for9 <= M <= 32); larger M and prefill use dequantize + cuBLAS.INFINILM_FP8_FUSED_GEMM=0forces the fallback.INFINILM_FP8_MARLIN=1) infp8_blockwise.cpp— currently blocked by an InfiniCore sm_120 kernel defect, see "Notes for Reviewers".KVQuantAlgo::FP8(csrc/config/quant_config.*), F8 cache allocation plus per-layer F32k_scale/v_scale(csrc/cache/kv_cache.*,csrc/global_state/forward_context.hpp,csrc/models/infinilm_model.cpp), scale passing incsrc/layers/attention/backends/paged_attn.cpp. Other attention backends reject FP8 KV explicitly at construction.kv_cache_dtypeexposed throughEngineConfig,LLM, and the server entry points (python/infinilm/config/engine_config.py,python/infinilm/llm/llm.py,python/infinilm/server/inference_server.py);"fp8"handled inmodeling_utils.py::parse_dtype.python/infinilm/modeling_utils.py:_resolve_preserve_configgeneralizes dtype-preservation to any config withquantization_config(keeps float8 dtypes andweight_scale/weight_scale_invtensors as stored);_cast_fp8_scales_to_fp32.test/models/qwen3_fp8/test_fp8_checkpoint_loading.py.pyproject.toml; log expected shutdown exceptions as warnings, not fatal, incsrc/engine/rank_worker.cpp; rounding carry in F16 scale conversion for the Marlin path (csrc/layers/quantization/gptq_marlin.cpp).Motivation
InfiniLM already supports GPTQ/AWQ (Marlin), MXFP4 and INT8 KV cache, but had no FP8 weight quantization at all — the release format of DeepSeek-V3 main weights and the Qwen3 FP8 series (e.g.
Qwen/Qwen3-8B-FP8, dynamic activation quantization). This PR adds the full path end to end, plus FP8 KV cache (halves cache capacity and decode attention read bandwidth). This is a course project submission (LLAISYS 2026 spring camp, project #4 — quantization and low-precision inference); design docfp8_quant_proposal.md, weekly reports and all raw artifacts (logs, JSON, scripts) referenced below asdev_fp8/...live on the fork branchbackup/fp8-lm-full-history, intentionally kept out of this PR.Before/after, decode throughput (Qwen3-8B-FP8 vs Qwen3-8B, RTX 5090 sm_120,
examples/bench.py --enable-paged-attn --warmup, input 128 / output 256, greedy; methodology indev_fp8/w3~w5, w8reports):Accuracy / memory (targets from the project proposal: PPL deviation < 1%, C-Eval/MMLU within 0.5 pt of BF16, weight memory <= 60% of BF16):
FP8 KV cache (same FP8 weights; baseline is paged BF16 KV): PPL 18.8724 vs 18.8772 (-0.026%); C-Eval 74.29% vs 74.67% (-0.37 pt); MMLU 69.54% vs 69.32% (+0.22 pt) — all within the ±0.5 pt noise band. KV bytes per token (Qwen3-8B): 147456 B -> 73728 B + 2304 B scales = ~1.94x compression.
Closes # — no tracking issue; course submission, see above. Depends on InfiniTensor/InfiniCore#1568.
Type of Change
feat— new feature / new modelfix— bug fix (rank_worker shutdown logging; Marlin F16 scale rounding carry)build/ci— build system or CI configuration (pyproject.tomlruntime dependencies/extras)Test Results of Involved Models on Supported Platforms (Please attach screenshots)
Platform: NVIDIA RTX 5090 (sm_120, 32 GB), driver 610.43.02, CUDA 12.8/13.3. Model:
Qwen/Qwen3-8B-FP8(snapshot 220b46e3); baselineQwen/Qwen3-8B(b968826d); vLLM 0.28.0 cross-check (torch 2.13.0+cu130).examples/bench.py): tables in "Benchmark / Performance Impact" below; raw logs indev_fp8/results/perf_bench.log,perf_bf16_bs48.log, W6/W7/W8 logs (dev_fp8/results/w6_perf3.log,w8_chain.log, ...).dev_fp8/results/{ppl,ceval,mmlu}_*.json, runnersfp8_ppl_eval.py,fp8_mcq_eval.py,run_accuracy_evals.sh. vLLM parity: BF16 C-Eval identical between engines (74.37%), FP8 MMLU within 0.11 pt.python -m unittest test.models.qwen3_fp8.test_fp8_checkpoint_loading— developed and run on RTX 5090 during the W3 phase; not re-run on the final rebased branch (no NVIDIA GPU on the machine preparing this submission).dev_fp8/results/w7_e2e_parity.py); bs=16 mma vs naive coherent with identical openings.examples/test_infer.py) and service test: not re-run on the final rebased branch (same reason as above); see Checklist for status.Other platforms: no behavior change on non-NVIDIA platforms (FP8 paths are opt-in; FP8 KV on non-paged backends fails explicitly at construction). Untested on non-NVIDIA hardware — no device access; see Checklist/Testing.
Benchmark / Performance Impact
Harness:
examples/bench.py --enable-paged-attn --warmup, input 128 / output 256, greedy, RTX 5090 (sm_120). Reproduce withdev_fp8/results/fp8_perf_bench.sh(build:fp8_build_core.sh/fp8_build_lm.sh, env:fp8_env.sh). Decode tables are in "Motivation" above.FP8 KV cache decode ITL (ms, output 128; same-session BF16 KV baseline;
dev_fp8/results/w7_splitkv_bench.sh):(bs=8/in=16384 OOMs at bench warmup for both dtypes — pre-existing bench behavior, unrelated to KV dtype.) Prefill TTFT overhead of FP8 KV: +0.5%~+3.0% (gather-dequant scratch).
INFINIOP_FLASH_DECODE_SPLITKV=0regression column matches the pre-split-kv numbers point-for-point, confirming the gain comes from split-kv alone.Remaining gaps (recorded honestly): bs=8 decode stays on the SIMT path (~60% of BF16); prefill still uses dequant + cuBLAS (bs=1 41.5% of BF16, bs=16 90.5%); vLLM FP8 is faster at bs>=8 (1224/2327 tok/s at bs=8/16) — the GEMM part of that gap at bs>=16 was closed by the mma path (382 -> 1794.6 tok/s), the remainder is attention/runtime, not re-measured against vLLM.
Notes for Reviewers
fp8_blockwise_dequantize,fp8_blockwise_gemm, FP8 KV in paged attention operators). CI here builds against InfiniCore main, so CI must be (re)triggered after that merges.INFINILM_FP8_MARLIN=1) and currently unusable on sm_120: the InfiniCore Marlin kernel deadlocks (CUDA 12.8) or produces wrong output for all bit widths (CUDA 13.2). Minimal repro and investigation:dev_fp8/marlin_sm120_issue.md(+dev_fp8/marlin_sm120_repro/). The default path does not touch Marlin.INFINILM_FP8_FUSED_GEMM=0,INFINIOP_FP8_GEMM_MMA=0.test/bench/test_benchmark.pygenerative C-Eval scoring breaks on Qwen3 chat output (answer extraction inspects only the first 2 characters;--max-new-tokens 8truncates), so accuracy was measured with logprob-MCQ (lm-eval-harness protocol, identical prompts/data/scoring on both engines) — seedev_fp8/w5_eval_report.md§1.pyproject.toml,rank_worker.cpp,gptq_marlin.cpprounding) are small and listed in Summary; happy to split into separate PRs if reviewers prefer.CI / ChatOps
CI runs on this PR automatically; it builds against InfiniCore main, so a green run requires the InfiniCore PR linked above to merge first. Will retrigger (or ask a maintainer for
/retest) after that lands.Checklist
Title, Branch, and Commits
feat: ...).<type>/xxx-yyyy-zzzz—feat/fp8-blockwise-quantization.feat:/test:/build:/fix:).main— branch is linear and freshly based on currentmain.fixup!/squash!/wipcommits remain.Scope and Design
INFINILM_FP8_*env switches are documented fallback toggles, not debug leftovers.scripts/format.py).EngineConfig.kv_cache_dtype,LLM(...)/inference_serverparameters with docstrings;KVQuantAlgo::FP8.General Code Hygiene (applies to all languages)
C++ Specific (if C++ files changed)
new/delete; RAII / existing allocators.scripts/format.py.csrc/models/llama_legacy/.Python Specific (if Python files changed)
scripts/format.py.python/infinilm/auto_config.py.Testing
examples/test_infer.py) — not re-run on the final rebased branch (no NVIDIA GPU on the machine preparing this submission); during development parity was verified at theInferEnginelevel with the scripts indev_fp8/results/(E2E greedy parity above), andkv_cache_dtypehas since been added to theLLMentry points.examples/bench.py) — extensively, see Benchmark section.test/bench/test_benchmark.py) — skipped with reason: its generative C-Eval scoring cannot parse Qwen3 chat answers (see Notes for Reviewers); accuracy covered instead by full logprob-MCQ evals (C-Eval val 1346 / MMLU test 14042) with raw data indev_fp8/results/.python/infinilm/server/inference_server.py+scripts/test_perf.py) — not run; server-side change is limited tokv_cache_dtypeparameter passthrough, covered by the config-level wiring.Build, CI, and Tooling
dev_fp8/results/fp8_build_core.sh,fp8_build_lm.sh; W8 build logw8_build2.log).Documentation
fp8_quant_proposal.mdanddev_fp8/*.md(on the fork branchbackup/fp8-lm-full-history);kv_cache_dtypedocumented inEngineConfig/LLM/server docstrings.README.md/MODELS.mdunchanged (no new model family — this enables existing Qwen3 FP8 checkpoints).Security and Safety
dev_fp8/is not part of this PR; itsresults/fp8_ssh.pyis a password-auth wrapper and the password is never stored in the repo).K % 128 == 0,block_k % 128 == 0,block_n % 16 == 0) are enforced at InfiniCore descriptor creation, so mis-shapes fail loudly instead of miscomputing.