Repository navigation
feat(flint): add nvfp4 modelopt and gguf support - #797
Conversation
jason-fxz
left a comment
There was a problem hiding this comment.
Thanks for the thorough PR. I checked it on an RTX PRO 6000 (sm_120) at f48684f with all three checkpoints: the BF16 RecursiveSI/flint, the ModelOpt coldgptq export and the official NVFP4 GGUF.
What I verified
-
I wrote an independent fp32 PyTorch reference on the BF16 weights, following the SGLang kit's semantics, and teacher-forced FreeToken's greedy outputs through it. The prompt set includes a 1028-token prompt, so SWA, NoPE and the sinks are all exercised.
Path Greedy tokens agreeing with the reference First-token logit cosine BF16 311/316 0.99997 ModelOpt NVFP4 (native W4A4 on sm_120) 314/325 ≥ 0.9997 GGUF NVFP4 300/325 ≥ 0.998 The BF16 divergences are all near-ties. llama.cpp's
flint.cpp(#30928) has the same semantics. -
Per-module quant methods at runtime match the checkpoints. ModelOpt has NVFP4 experts and BF16 for everything else. In the GGUF, all linears and lm_head are NVFP4, while token_embd and the router are BF16.
-
Your e2e passes 2/2 here, after raising the startup deadline locally. My own serving checks with default flags pass on both formats. They cover reasoning split, tool call plus tool result, streamed tool calls, 4 concurrent requests, and a 23k-token passkey both cold and with prefix reuse.
So the model support itself looks correct. Requests:
-
Router: use the fused kernel. Please rebase onto main and route with
fused_topk_sigmoidfrom #804, as glm4_moe and minimax_m2 now do.- On sm_120 it picks identical experts, with weights equal to within 1.5e-8.
- It cuts routing over the 47 layers from 786 to 187 µs per decode step at bs=1, and from 1921 to 328 µs for a 2048-token prefill.
- The zero
expert_biascan stay as the kernel's bias argument. - Please keep the kit's bf16 router logits (
self.gate.forward(x).float()). fp32 logits, as glm4_moe uses, change the top-6 set for 2.3% of token-layer routes on Flint.
-
No per-model e2e. Please remove
tests/e2e/test_flint.py, its row intests/README.md, and the e2e part ofdocs/flint.md. We don't keep a separate e2e file per model. Run the generic gates (tests/e2e/test_aime.pyandtests/e2e/test_cache_rebuild.pywithFREETOKEN_TEST_MODEL) and put the results in the PR description. -
Own CausalLM class.
FlintForCausalLMsubclassesqwen3_moe.Qwen3MoeForCausalLM. Please give Flint its ownBaseLLMModelsubclass, a few lines for embed and lm_head, as minimax_m2, gpt_oss and glm4_moe do. Model families shouldn't import each other's model classes. -
Parsers.
flintas a tool-call parser is only an alias ofGlm47Detector. Auto-detection can returnglm47instead of adding a new user-facing choice.- For reasoning,
FlintReasoningParsersilently ignores theforce_reasoningit is given. Please instead registerflintwithThinkReasoningParserand add aparser_name == "flint"branch (force_reasoning = False) ingeneration._make_reasoning_parser, next to minimax_m3 and gemma4. That keeps the per-family rule in one place.
-
Tests. Please drop the tests that restate the implementation (see the "What earns a place here" rules in
tests/README.md):test_rejects_unimplemented_config_semanticsonly checks the config guards.test_shared_expert_sees_input_before_routed_kernel_overwrites_itmirrors the call order inFlintMoE.forward.test_attention_projection_norm_rope_and_sinks_match_torchmocks the attention backend, so the sinks and SWA kernels are not covered; the reference comparison above covers them.
The weight-reader, EOG-ids and official GGUF tokenizer/config tests are worth keeping.
Nits:
docs/models.mdandSUPPORTED_MODELSinaot_models.pyput Flint first; please keep the existing order.- The
gguf/tokenizer.pycomment on_EOG_NAMESlost its pointer to llama.cpp'sllama_vocab::impl::load; please keep it. pre in ("joyai-llm", "flint")with an inline ternary would read better as apre -> splitsdict.
f48684f to
711a86e
Compare
|
@jason-fxz Thanks for the detailed review and independent validation. Updated in 711a86e, rebased onto main at 507a8bf.
Validation on RTX 3060 / TP=1:
The generic gates now support UUID GPU selection and GGUF/offload arguments. The short generation probe accepts nonempty visible text or reasoning, since Flint can spend its entire 32-token budget in the reasoning channel. Full commands and validation limits are in the updated PR description. These runs reused installed native extensions and prebuilt kernel caches; they do not establish a clean native build or full-suite, multi-GPU or Blackwell validation. Ready for another look. |
FlashML-org#805 FlashML-org#797 FlashML-org#795 adopted (FlashML-org#805 replaces our 8236bb3 kernel changes), FlashML-org#806 and FlashML-org#810 deferred. Assisted-by: Claude Opus 5.5
jason-fxz
left a comment
There was a problem hiding this comment.
Thanks, all five points are addressed. Re-checked 711a86e on sm_120: greedy output still matches the fp32 reference as in round one, and unit tests, serving checks on both formats and the generic gates pass.
Two last requests:
- Please remove
docs/flint.mdand make the Flint row indocs/models.mdplain text like the others. We don't keep per-model pages indocs/, and the checkpoint list is already in the table. - Please move the
tests/e2e/test_aime.pychange (the child-process free-memory probe) out of this PR. It fixes UUID GPU selection for every model, not Flint, so it belongs in its own PR.
After that this is ready to merge.
Recipe from a sweep on the 4090: fp8 KV, one request, --embed-device cpu, memory ratio 0.95 -> 97k context, ~220-228 tok/s decode (offload/fp8 ~200-212, hybrid -5 %, nvfp4 KV 154k but -10 %, ModelOpt export 133-141). Runs on next-speed now that it carries FlashML-org#797. Assisted-by: Claude Opus 5.5
|
@jason-fxz Thanks for rechecking. Both final requests are addressed in 6ee048e:
Updated the description to link the extracted fix and identify the validation revision. Tests were not rerun after this scope cleanup; the previously reported results remain those from 711a86e, which you independently rechecked. |
Summary
Related to #786. Add Recursive Flint support for BF16-layout safetensors, the ModelOpt NVFP4 export and the official NVFP4 GGUF.
glm47tool parsing and optionalflintreasoning parsing.Final review cleanup
Addressed the second review in
6ee048e3cca92da2a85cdc9ca6fd21cceed527ad:docs/flint.md; the Flint name indocs/models.mdis now plain text and the checkpoint links remain in the table.tests/e2e/test_aime.pyexactly to the base version. The generic UUID-aware free-memory probe is now isolated in fix(test): respect cuda device visibility in aime memory probe #821, based on currentmain, with no Flint implementation changes.No tests were rerun for this final scope cleanup. The results below were recorded on
711a86ef11c67bbe6c69ff59370594f64c6ef1b8, before extraction. In particular, the historical UUID-selected AIME command used the probe now in #821; it is not a fresh run of the latest #797 tree. The reviewer also independently rechecked that revision on sm_120.Changes following review
Rebased onto
mainat507a8bfa81d5c305e047661576165a74068e5ab0, preserving the Qwen GGUF NFC normalization added upstream.fused_topk_sigmoidfrom perf(moe): fuse sigmoid + selection-bias routing into a triton kernel #804. Keepself.gate.forward(x).float()so the router projection still produces BF16 logits before FP32 sigmoid/top-k. Keep the zero selection-bias tensor.FlintForCausalLMnow directly subclassesBaseLLMModeland owns its model/lm_head; no import of another family's model class.flinttool-parser choice and useglm47. Registerflintreasoning withThinkReasoningParser;_make_reasoning_parserownsforce_reasoning=Falsefor this family. Streaming/non-streaming tests cover tools and generic thinking flags.llama_vocab::impl::loadpointer and use a pre-tokenizer-to-splits dictionary.The cache-rebuild gate needed small harness adjustments for this environment:
FREETOKEN_REBUILD_EXTRA_ARGSfor offloaded models. Its short generation probe accepts text in either content or reasoning: Flint can spend the entire 32-token probe in its thought channel. Empty output in both channels still fails.Local validation at
711a86ebefore final scope cleanupEnvironment and checkpoints
PYTHONPATH=pythonin an isolated uv-created venv. Native extensions (including ggml-cpu variants) and prebuilt kernels were reused fromfreetoken 0.2.0+gfe5a5ec9c/freetoken-kernel-cache 0.2.0+cu130.gfe5a5ec9c. No clean native-extension/CUDA build was performed for this revision; this PR changes no C++/CUDA source.FutureMLS-Lab/flint-nvfp4-coldgptq56f37fcfc7bd17834512ec2e53696b8da15f3658RecursiveSI/flint-nvfp4-gguf,flint-nvfp4.gguf6ec43e19b565fc8d560e2d04985d7724f20673bfRecursiveSI/flintreference config/tokenizer2a247be0c0aec895ccb0ac7ca92229ec6df37288Commands below ran from the source checkout; the sibling venv/checkpoint paths reflect the local validation layout:
Targeted regressions: 709 passed, 24 skipped, 18 deselected, 1 warning
Result:
709 passed, 24 skipped, 18 deselected, 1 warning in 14.43s.The warning is the existing Starlette TestClient/httpx deprecation. The 18 slow router cases were then run explicitly:
"$PYTHON" -m pytest tests/moe/test_fused_moe.py::test_fused_topk_sigmoid_matches_torch_reference -qResult:
18 passed in 12.35s.Generic AIME gate: 1 passed
Dataset:
math-ai/aime25, revision563bb8404243c5f09de6ec262f2db674fe5bce9b,test.jsonl; convertproblemto the gate'spromptkey and preserveanswerand row order. AIME 2025 problem 1, pass@3, 8192-token maximum per sample. This is one problem, not an AIME-suite accuracy claim. Sampling comes from the checkpoint: temperature 1.0, top-p 0.95, top-k 20.Result:
1 passed, 1 warning in 61.45s; existing CUDA allocator deprecation warning.Generic cache-rebuild gate: 1 passed per format
Each format ran in a separate process. The gate exercises real generation, cold/warm prefix reuse and flush, KV growth from 4000 to 6000 pages with graph re-capture, and rejection of an unfittable target while preserving the serving engine.
Results: ModelOpt
1 passed in 40.96s; official GGUF1 passed in 51.10s.Scope and limitations
dgpp_mixedand MLX layouts remain unsupported. The separate Flint SGLang XGrammar backend is not ported.Implementation and local test execution were assisted by Codex.