Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 5 additions & 4 deletions src/models/model_type.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ namespace Generators {
struct ModelType {
inline static bool IsLLM(const std::string& model_type) {
// Large-language model (LLM)
static constexpr std::array<std::string_view, 32> LLM = {"chatglm", "decoder", "ernie4_5", "gemma", "gemma2", "gemma3_text", "gemma3_vl_text", "gemma4_text", "gpt2", "gptoss", "granite", "granitemoehybrid", "hunyuandensev1", "internlm2", "lfm2", "lfm2_vl_text", "llama", "mistral", "mistral3_text", "nemotron", "olmo", "phi", "phimoe", "phi3", "phi3small", "qwen2", "qwen2_5_vl_text", "qwen3", "qwen3_vl_text", "qwen3_5_moe_text", "qwen3_5_text", "smollm3"};
static constexpr std::array<std::string_view, 33> LLM = {"chatglm", "decoder", "ernie4_5", "gemma", "gemma2", "gemma3_text", "gemma3_vl_text", "gemma4_text", "gpt2", "gptoss", "granite", "granitemoehybrid", "hunyuandensev1", "internlm2", "lfm2", "lfm2_moe", "lfm2_vl_text", "llama", "mistral", "mistral3_text", "nemotron", "olmo", "phi", "phimoe", "phi3", "phi3small", "qwen2", "qwen2_5_vl_text", "qwen3", "qwen3_vl_text", "qwen3_5_moe_text", "qwen3_5_text", "smollm3"};
return std::find(LLM.begin(), LLM.end(), model_type) != LLM.end();
}

Expand Down Expand Up @@ -75,9 +75,10 @@ struct ModelType {

inline static bool IsLFM2(const std::string& model_type) {
// Liquid Foundation Model 2: hybrid attention/conv architecture with conv state cache.
// "lfm2_vl_text" is an LFM2-VL checkpoint exported as a plain text decoder; it has the same
// conv/attention layer mix and needs the same conv state cache.
return model_type == "lfm2" || model_type == "lfm2_vl_text";
// The MoE variant (LFM2-8B-A1B, LFM2.5-8B-A1B, LFM2-24B-A2B) has the same runtime shape, and
// "lfm2_vl_text" is an LFM2-VL checkpoint exported as a plain text decoder; both have the same
// conv/attention layer mix and need the same conv state cache.
return model_type == "lfm2" || model_type == "lfm2_moe" || model_type == "lfm2_vl_text";
}
};

Expand Down
10 changes: 9 additions & 1 deletion src/python/py/models/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ The tool currently supports the following model architectures.
- HunYuan Dense V1
- InternLM2
- LFM2 (text and the decoder of LFM2-VL / LFM2.5-VL)
- LFM2 MoE
- Llama
- Mistral
- Nemotron
Expand Down Expand Up @@ -599,7 +600,8 @@ python builder.py -m model_name -o path_to_output_folder -p int4 -e execution_pr
##### QMoE Block Size

This scenario is for when you want to set the block size for QMoE expert weights.
Set `qmoe_block_size` to `0` or a negative value for per-channel quantization. CUDA block-wise QMoE supports only `32`, `64`, or `128`; the default is `32` except for TRT-RTX, which defaults to `128`.
Set `qmoe_block_size` to `0` or a negative value for per-channel quantization. Block-wise QMoE on CPU, CUDA, and WebGPU supports only `32`, `64`, or `128`; TRT-RTX also accepts `16` and `256`. The default is `32` except for TRT-RTX, which defaults to `128`.
WebGPU block-wise QMoE requires both `hidden_size` and `moe_intermediate_size` to be divisible by `qmoe_block_size`. Raw block-wise INT4 QMoE requires both dimensions to be even.

```bash
# From wheel:
Expand All @@ -609,6 +611,12 @@ python -m onnxruntime_genai.models.builder -m model_name -o path_to_output_folde
python builder.py -m model_name -o path_to_output_folder -p int4 -e execution_provider --extra_options qmoe_block_size=128
```

On CPU and WebGPU the block-wise expert weights use ONNX Runtime's MatMulNBits encoding with signed block scales: each block's max-magnitude element maps exactly to the lowest quantized value, so no extreme is clipped. This encoding has been validated end to end against the Hugging Face reference on CPU, and the WebGPU QMoE kernel consumes the MatMulNBits layout directly. TRT-RTX keeps the builder's original symmetric encoding with positive block scales until the signed-scale grid has been measured on that execution provider.

The CPU `QMoE` kernel dequantizes the experts to fp32 on every call unless the environment variable `ORT_USE_MLAS_Q4_GEMM_MOE=1` is set at runtime, which enables its AVX-512 MLAS Q4 fast path. That fast path re-quantizes to the same grid as this encoding, so enabling it only changes results at fp32 rounding level.

ONNX Runtime builds that include [microsoft/onnxruntime#32644](https://github.com/microsoft/onnxruntime/pull/32644) run block-wise experts on the MLAS QNBit GEMM (`MatMulNBits`) kernels directly, without the environment variable. For LFM2.5-8B-A1B int4 on a 30-core x86 server, CPU decode went from about 0.8 s per token with the default dequantize-then-GEMM path to 35 ms per token with `ORT_USE_MLAS_Q4_GEMM_MOE=1` and 15 ms per token with #32644.

##### QMoE Weights Prepacked

This scenario is for when you want to control the CUDA QMoE expert weight layout. The default value is `-1`, which lets the builder choose the layout automatically. Use `0` to export raw weights and let CUDA prepack them at runtime, or `1` to export CUTLASS-prepacked weights.
Expand Down
8 changes: 7 additions & 1 deletion src/python/py/models/builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
HunyuanDenseV1Model,
InternLM2Model,
LFM2Model,
LFM2MoEModel,
LlamaModel,
Mistral3TextModel,
MistralModel,
Expand Down Expand Up @@ -562,6 +563,9 @@ def create_model(
onnx_model = InternLM2Model(config, io_dtype, onnx_dtype, execution_provider, cache_dir, extra_options)
elif config.architectures[0] == "Lfm2ForCausalLM":
onnx_model = LFM2Model(config, io_dtype, onnx_dtype, execution_provider, cache_dir, extra_options)
elif config.architectures[0] == "Lfm2MoeForCausalLM":
onnx_model = LFM2MoEModel(config, io_dtype, onnx_dtype, execution_provider, cache_dir, extra_options)
onnx_model.model_type = "lfm2_moe"
elif config.architectures[0] == "Lfm2VlForConditionalGeneration":
onnx_model = LFM2Model(config, io_dtype, onnx_dtype, execution_provider, cache_dir, extra_options)
# With the embedding layer excluded the decoder is one stage of the LFM2-VL vision pipeline;
Expand Down Expand Up @@ -728,7 +732,9 @@ def get_args():
Default value is 32.
qmoe_block_size = <=0/16/32/64/128/256: Specify the block size for QMoE expert weights quantization.
Set <= 0 for per-channel quantization. Default is 128 for TRT-RTX, 32 for others.
CUDA block-wise QMoE supports 32/64/128 only.
CPU, CUDA, and WebGPU block-wise QMoE support 32/64/128 only; TRT-RTX also accepts 16/256.
WebGPU requires hidden_size and moe_intermediate_size to be divisible by qmoe_block_size.
Raw block-wise INT4 QMoE requires both dimensions to be even.
Supported EPs: CPU, CUDA, WebGPU, TRT-RTX.
qmoe_weights_prepacked = -1/0/1: Specify the CUDA QMoE expert weight layout.
-1 lets the builder choose automatically, 0 exports raw weights for runtime prepacking, and 1 exports CUTLASS-prepacked weights.
Expand Down
3 changes: 2 additions & 1 deletion src/python/py/models/builders/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
from .granite import GraniteModel, GraniteMoEHybridModel
from .hunyuan import HunyuanDenseV1Model
from .internlm import InternLM2Model
from .lfm2 import LFM2Model
from .lfm2 import LFM2Model, LFM2MoEModel
from .llama import LlamaModel
from .mistral import Mistral3TextModel, MistralModel
from .mtp import MTPModel
Expand Down Expand Up @@ -58,6 +58,7 @@
"HunyuanDenseV1Model",
"InternLM2Model",
"LFM2Model",
"LFM2MoEModel",
"LlamaModel",
"MTPModel",
"Mistral3TextModel",
Expand Down
Loading
Loading