feat: unify text encoder selection across H3 variants (#2340)

This commit is contained in:
leejet authored and GitHub committed 2026-11-13 00:23:54 +08:00
1 parent 8b1e9df27d
commit b9d7176bf8
10 files changed
+105 -39

No files matched your search

+25 -4
View File
@@ -12,12 +12,31 @@ Pass the four MiniMax-H3 components separately:
- `--diffusion-model`: MiniMax-H3 diffusion transformer - `--diffusion-model`: MiniMax-H3 diffusion transformer
- `--vae`: MiniMax-H3 video VAE - `--vae`: MiniMax-H3 video VAE
- `--audio-vae`: MiniMax-H3 audio VAE - `--audio-vae`: MiniMax-H3 audio VAE
- `--llm`: the MiniMax-H3 Qwen3-VL-32B text encoder checkpoint - `--llm`: the MiniMax-H3 Qwen3-VL-32B text encoder, or Qwen3-VL-4B with the P-H3 text connector
The text encoder must be the MiniMax-H3 variant: Qwen3-VL-32B truncated to 50 For the 32B option, use the MiniMax-H3 variant: Qwen3-VL-32B truncated to 50
language layers and exported without the final language-model normalization. language layers and exported without the final language-model normalization.
Its Qwen3-VL vision tower, including the three DeepStack mergers, must also be
present. If the vision tower is stored separately, pass it with `--llm_vision`. For the smaller 4B option, pass the P-H3-Image text encoder, which bundles the
connector, with `--llm`. Alternatively, pass Qwen3-VL-4B with `--llm` and the
[separate P-H3 text connector](https://huggingface.co/PrunaAI/P-H3-Image/blob/main/text_connector/diffusion_pytorch_model.safetensors)
with `--embeddings-connectors`. For example, replace the `--llm` argument in the
commands below with either:
```sh
--llm ..\models\text_encoders\p-h3-image_text_encoder_int8_convrot.safetensors
```
or:
```sh
--llm ..\models\text_encoders\qwen3vl_4b.gguf --embeddings-connectors ..\models\text_encoders\p-h3-image_text_connector.safetensors
```
Reference-image and reference-video conditioning needs the matching encoder's
Qwen3-VL vision tower, including the three DeepStack mergers. If stored
separately, pass it with `--llm_vision`. Keep using the MiniMax-H3 video and audio
VAEs with either text encoder.
Both the original time-embedder DiT and the smaller AdaLN curve-table variant Both the original time-embedder DiT and the smaller AdaLN curve-table variant
are detected from their weights. are detected from their weights.
@@ -30,6 +49,8 @@ are detected from their weights.
- Download qwen3vl_32b_minimax_h3 - Download qwen3vl_32b_minimax_h3
- safetensors: https://huggingface.co/Comfy-Org/MiniMax-H3/tree/main/text_encoders - safetensors: https://huggingface.co/Comfy-Org/MiniMax-H3/tree/main/text_encoders
- gguf: https://huggingface.co/leejet/MiniMax-H3-GGUF/tree/main - gguf: https://huggingface.co/leejet/MiniMax-H3-GGUF/tree/main
- Or download the P-H3-Image text encoder (Qwen3-VL-4B with the text connector)
- safetensors: https://huggingface.co/PrunaAI/P-H3-Image-Comfy/tree/main/text_encoders
- Download vae - Download vae
- safetensors: https://huggingface.co/Comfy-Org/MiniMax-H3/tree/main/vae - safetensors: https://huggingface.co/Comfy-Org/MiniMax-H3/tree/main/vae
- Download audio vae - Download audio vae
+14 -1
View File
@@ -1,6 +1,6 @@
# How to Use # How to Use
P-H3-Image supports text-to-image and reference-image editing. It uses a MiniMax-H3 diffusion transformer, a single-frame VAE, and Qwen3-VL-4B with a text connector. Prompt enhancement is not included. P-H3-Image supports text-to-image and reference-image editing. It uses a MiniMax-H3 diffusion transformer, a single-frame VAE, and either Qwen3-VL-4B with a text connector or the MiniMax-H3 Qwen3-VL-32B text encoder. Prompt enhancement is not included.
## Download weights ## Download weights
@@ -10,9 +10,22 @@ P-H3-Image supports text-to-image and reference-image editing. It uses a MiniMax
- safetensors: https://huggingface.co/PrunaAI/P-H3-Image-Comfy/tree/main/vae - safetensors: https://huggingface.co/PrunaAI/P-H3-Image-Comfy/tree/main/vae
- Download Qwen3-VL-4B with the text connector - Download Qwen3-VL-4B with the text connector
- https://huggingface.co/PrunaAI/P-H3-Image-Comfy/tree/main/text_encoders - https://huggingface.co/PrunaAI/P-H3-Image-Comfy/tree/main/text_encoders
- Or download the MiniMax-H3 Qwen3-VL-32B text encoder
- safetensors: https://huggingface.co/Comfy-Org/MiniMax-H3/tree/main/text_encoders
- gguf: https://huggingface.co/leejet/MiniMax-H3-GGUF/tree/main
Alternatively, reuse Qwen3-VL-4B, including its vision tower, with the [separate text connector](https://huggingface.co/PrunaAI/P-H3-Image/blob/main/text_connector/diffusion_pytorch_model.safetensors). Save the connector as `p-h3-image_text_connector.safetensors` and pass it with `--embeddings-connectors`. Alternatively, reuse Qwen3-VL-4B, including its vision tower, with the [separate text connector](https://huggingface.co/PrunaAI/P-H3-Image/blob/main/text_connector/diffusion_pytorch_model.safetensors). Save the connector as `p-h3-image_text_connector.safetensors` and pass it with `--embeddings-connectors`.
The MiniMax-H3 Qwen3-VL-32B encoder does not need a text connector. To use it in
either example below, omit `--embeddings-connectors` and replace `--llm` with:
```sh
--llm ..\models\text_encoders\qwen3vl_32b_minimax_h3-Q4_K_M.gguf
```
Reference-image editing needs the matching encoder's vision tower. If stored
separately, pass it with `--llm_vision`.
Use the P-H3 image VAE; the MiniMax-H3 video VAE is not compatible. No audio VAE is needed. Use the P-H3 image VAE; the MiniMax-H3 video VAE is not compatible. No audio VAE is needed.
## Examples ## Examples
@@ -3,7 +3,7 @@
"id": "hyperflow-h3-flash-fl2va", "id": "hyperflow-h3-flash-fl2va",
"name": "HyperFlow H3 Flash FL2VA", "name": "HyperFlow H3 Flash FL2VA",
"size_multiple": 32, "size_multiple": 32,
"description": "Generate video and audio from text, optionally with start/end images. Use the HyperFlow H3 Flash FL2VA transformer. Reuses MiniMax-H3 components with the HyperFlow 6-step schedule. Audio VAE is optional for silent video.", "description": "Generate video and audio from text, optionally with start/end images. Use the HyperFlow H3 Flash FL2VA transformer. Reuses MiniMax-H3 components with either the MiniMax-H3 Qwen3-VL-32B text encoder or Qwen3-VL-4B with the P-H3 text connector and the HyperFlow 6-step schedule. Audio VAE is optional for silent video.",
"model_url": "https://huggingface.co/VideoRebirthAI/hyperflow-h3-flash", "model_url": "https://huggingface.co/VideoRebirthAI/hyperflow-h3-flash",
"slots": [ "slots": [
{ {
@@ -14,7 +14,17 @@
{ {
"id": "llm", "id": "llm",
"required": true, "required": true,
"label": "MiniMax-H3 Qwen 3 VL 32B Text Encoder" "label": "MiniMax-H3 Qwen 3 VL 32B or P-H3 Qwen 3 VL 4B Text Encoder"
},
{
"id": "llm_vision",
"required": false,
"label": "Vision tower (when not included in the text encoder)"
},
{
"id": "embeddings_connectors",
"required": false,
"label": "P-H3 Text Connector (for Qwen 3 VL 4B, when not included in the text encoder)"
}, },
{ {
"id": "vae", "id": "vae",
@@ -3,7 +3,7 @@
"id": "hyperflow-h3-flash-ref2va", "id": "hyperflow-h3-flash-ref2va",
"name": "HyperFlow H3 Flash Ref2VA", "name": "HyperFlow H3 Flash Ref2VA",
"size_multiple": 32, "size_multiple": 32,
"description": "Generate video and audio with reference images. Use the HyperFlow H3 Flash Ref2VA transformer; video/audio references are not validated for these weights. Reuses MiniMax-H3 components with the HyperFlow 6-step schedule. Audio VAE is optional for silent video.", "description": "Generate video and audio with reference images. Use the HyperFlow H3 Flash Ref2VA transformer; video/audio references are not validated for these weights. Reuses MiniMax-H3 components with either the MiniMax-H3 Qwen3-VL-32B text encoder or Qwen3-VL-4B with the P-H3 text connector and the HyperFlow 6-step schedule. Reference images need the matching vision tower. Audio VAE is optional for silent video.",
"model_url": "https://huggingface.co/VideoRebirthAI/hyperflow-h3-flash", "model_url": "https://huggingface.co/VideoRebirthAI/hyperflow-h3-flash",
"slots": [ "slots": [
{ {
@@ -14,7 +14,17 @@
{ {
"id": "llm", "id": "llm",
"required": true, "required": true,
"label": "MiniMax-H3 Qwen 3 VL 32B Text Encoder" "label": "MiniMax-H3 Qwen 3 VL 32B or P-H3 Qwen 3 VL 4B Text Encoder"
},
{
"id": "llm_vision",
"required": false,
"label": "Vision tower (required for reference images when not included in the text encoder)"
},
{
"id": "embeddings_connectors",
"required": false,
"label": "P-H3 Text Connector (for Qwen 3 VL 4B, when not included in the text encoder)"
}, },
{ {
"id": "vae", "id": "vae",
+12 -2
View File
@@ -3,7 +3,7 @@
"id": "minimax-h3-fl2va", "id": "minimax-h3-fl2va",
"name": "MiniMax H3 FL2VA", "name": "MiniMax H3 FL2VA",
"size_multiple": 32, "size_multiple": 32,
"description": "Generate video and audio, optionally with start/end images. Supports full and pruned FL2VA weights. Audio VAE is optional for silent video.", "description": "Generate video and audio, optionally with start/end images. Supports full and pruned FL2VA weights with either the MiniMax-H3 Qwen3-VL-32B text encoder or Qwen3-VL-4B with the P-H3 text connector. Audio VAE is optional for silent video.",
"model_url": "https://huggingface.co/MiniMaxAI/MiniMax-H3", "model_url": "https://huggingface.co/MiniMaxAI/MiniMax-H3",
"slots": [ "slots": [
{ {
@@ -13,7 +13,17 @@
{ {
"id": "llm", "id": "llm",
"required": true, "required": true,
"label": "MiniMax-H3 Qwen 3 VL 32B Text Encoder" "label": "MiniMax-H3 Qwen 3 VL 32B or P-H3 Qwen 3 VL 4B Text Encoder"
},
{
"id": "llm_vision",
"required": false,
"label": "Vision tower (when not included in the text encoder)"
},
{
"id": "embeddings_connectors",
"required": false,
"label": "P-H3 Text Connector (for Qwen 3 VL 4B, when not included in the text encoder)"
}, },
{ {
"id": "vae", "id": "vae",
+12 -2
View File
@@ -3,7 +3,7 @@
"id": "minimax-h3-ref2va", "id": "minimax-h3-ref2va",
"name": "MiniMax H3 Ref2VA", "name": "MiniMax H3 Ref2VA",
"size_multiple": 32, "size_multiple": 32,
"description": "Supply image, video or audio references with compatible Ref2VA weights. Audio VAE is optional for silent video.", "description": "Supply image, video or audio references with compatible Ref2VA weights. Use either the MiniMax-H3 Qwen3-VL-32B text encoder or Qwen3-VL-4B with the P-H3 text connector. Image/video references need the matching vision tower. Audio VAE is optional for silent video.",
"model_url": "https://huggingface.co/MiniMaxAI/MiniMax-H3", "model_url": "https://huggingface.co/MiniMaxAI/MiniMax-H3",
"slots": [ "slots": [
{ {
@@ -13,7 +13,17 @@
{ {
"id": "llm", "id": "llm",
"required": true, "required": true,
"label": "MiniMax-H3 Qwen 3 VL 32B Text Encoder" "label": "MiniMax-H3 Qwen 3 VL 32B or P-H3 Qwen 3 VL 4B Text Encoder"
},
{
"id": "llm_vision",
"required": false,
"label": "Vision tower (required for image/video references when not included in the text encoder)"
},
{
"id": "embeddings_connectors",
"required": false,
"label": "P-H3 Text Connector (for Qwen 3 VL 4B, when not included in the text encoder)"
}, },
{ {
"id": "vae", "id": "vae",
+3 -3
View File
@@ -3,7 +3,7 @@
"id": "p-h3-image", "id": "p-h3-image",
"name": "P-H3-Image", "name": "P-H3-Image",
"size_multiple": 32, "size_multiple": 32,
"description": "Supports text-to-image and reference-image editing with the fixed 8-step schedule. Use the P-H3 single-frame VAE and Qwen3-VL-4B with its vision tower. Supply the P-H3 text connector separately unless it is included in the text encoder.", "description": "Supports text-to-image and reference-image editing with the fixed 8-step schedule. Use the P-H3 single-frame VAE and either Qwen3-VL-4B with the P-H3 text connector or the MiniMax-H3 Qwen3-VL-32B text encoder. Reference images need the matching vision tower.",
"model_url": "https://huggingface.co/PrunaAI/P-H3-Image", "model_url": "https://huggingface.co/PrunaAI/P-H3-Image",
"slots": [ "slots": [
{ {
@@ -13,7 +13,7 @@
{ {
"id": "llm", "id": "llm",
"required": true, "required": true,
"label": "Qwen 3 VL 4B Text Encoder" "label": "MiniMax-H3 Qwen 3 VL 32B or P-H3 Qwen 3 VL 4B Text Encoder"
}, },
{ {
"id": "llm_vision", "id": "llm_vision",
@@ -23,7 +23,7 @@
{ {
"id": "embeddings_connectors", "id": "embeddings_connectors",
"required": false, "required": false,
"label": "P-H3 Text Connector (required when not included in the text encoder)" "label": "P-H3 Text Connector (for Qwen 3 VL 4B, when not included in the text encoder)"
}, },
{ {
"id": "vae", "id": "vae",
+13 -10
View File
@@ -2030,20 +2030,23 @@ struct LLMEmbedder : public Conditioner {
"text_encoders.llm", "text_encoders.llm",
enable_vision, enable_vision,
weight_manager); weight_manager);
// MiniMax-H3-Image also accepts the P-H3-Image text encoder in place of the MiniMax-H3 Qwen3-VL-32B encoder. if (sd_version_is_minimax_h3(version)) {
const bool has_ph3_connector = tensor_storage_map.find(std::string(PH3ImageTextConnectorRunner::prefix) + ".proj.weight") != const bool has_ph3_connector = tensor_storage_map.count(std::string(PH3ImageTextConnectorRunner::prefix) + ".proj.weight") != 0;
tensor_storage_map.end(); if (has_ph3_connector) {
if (version == VERSION_P_H3_IMAGE || (version == VERSION_MINIMAX_H3_IMAGE && has_ph3_connector)) {
if (llm->config.num_layers != 36 || llm->config.hidden_size != 2560) { if (llm->config.num_layers != 36 || llm->config.hidden_size != 2560) {
throw std::runtime_error(version == VERSION_P_H3_IMAGE throw std::runtime_error("The P-H3-Image text connector requires the Qwen3-VL-4B text encoder");
? "P-H3-Image requires the Qwen3-VL-4B text encoder"
: "The P-H3-Image text connector requires the Qwen3-VL-4B text encoder");
} }
ph3_connector = std::make_shared<PH3ImageTextConnectorRunner>(backend, tensor_storage_map, weight_manager); ph3_connector = std::make_shared<PH3ImageTextConnectorRunner>(backend, tensor_storage_map, weight_manager);
} else if (version == VERSION_MINIMAX_H3_IMAGE && llm->config.hidden_size == 2560) { } else if (llm->config.hidden_size == 2560) {
throw std::runtime_error( throw std::runtime_error(
"MiniMax-H3-Image with Qwen3-VL-4B requires the P-H3-Image text connector; " sd_format("%s with Qwen3-VL-4B requires the P-H3-Image text connector; "
"pass it with --embeddings-connectors or use the MiniMax-H3 Qwen3-VL-32B text encoder"); "pass it with --embeddings-connectors or use the MiniMax-H3 Qwen3-VL-32B text encoder",
sd_version_name(version)));
} else if (llm->config.hidden_size != 5120 || (llm->config.num_layers != 50 && llm->config.num_layers != 64)) {
throw std::runtime_error(
sd_format("%s requires the MiniMax-H3 Qwen3-VL-32B text encoder or Qwen3-VL-4B with the P-H3-Image text connector",
sd_version_name(version)));
}
} }
int pad_id = 151643; int pad_id = 151643;
if (arch == LLM::LLMArch::MISTRAL_SMALL_3_2 || arch == LLM::LLMArch::MINISTRAL_3_3B) { if (arch == LLM::LLMArch::MISTRAL_SMALL_3_2 || arch == LLM::LLMArch::MINISTRAL_3_3B) {
+1 -12
View File
@@ -581,7 +581,7 @@ SDVersion ModelLoader::get_sd_version() const {
if (tensor_storage_map.count("model.diffusion_model.audio_patch_proj.weight") == 0) { if (tensor_storage_map.count("model.diffusion_model.audio_patch_proj.weight") == 0) {
return VERSION_MINIMAX_H3_IMAGE; return VERSION_MINIMAX_H3_IMAGE;
} }
// P-H3 uses the same DiT architecture as H3; its VAE or connector identifies the image pipeline. // The connector is shared with H3 video; the single-frame VAE identifies P-H3.
auto decoder = tensor_storage_map.find("first_stage_model.decoder.proj_out.weight"); auto decoder = tensor_storage_map.find("first_stage_model.decoder.proj_out.weight");
if (decoder == tensor_storage_map.end()) { if (decoder == tensor_storage_map.end()) {
decoder = tensor_storage_map.find("vae.decoder.proj_out.weight"); decoder = tensor_storage_map.find("vae.decoder.proj_out.weight");
@@ -589,17 +589,6 @@ SDVersion ModelLoader::get_sd_version() const {
if (decoder != tensor_storage_map.end() && decoder->second.ne[1] == 768) { if (decoder != tensor_storage_map.end() && decoder->second.ne[1] == 768) {
return VERSION_P_H3_IMAGE; return VERSION_P_H3_IMAGE;
} }
for (const std::string prefix : {"", "embeddings_connectors."}) {
if (tensor_storage_map.count(prefix + "text_encoders.ph3_image_connector.proj.weight") ||
tensor_storage_map.count(prefix + "text_encoders.llm.text_connector.proj.weight")) {
return VERSION_P_H3_IMAGE;
}
}
auto connector = tensor_storage_map.find("embeddings_connectors.proj.weight");
if (connector != tensor_storage_map.end() && connector->second.logical_ne0() == 12800 &&
connector->second.ne[1] == 5120 && tensor_storage_map.count("embeddings_connectors.sink_embedding")) {
return VERSION_P_H3_IMAGE;
}
return VERSION_MINIMAX_H3; return VERSION_MINIMAX_H3;
} }
if (name == "model.diffusion_model.y_embedder.layer_pool.weight" && if (name == "model.diffusion_model.y_embedder.layer_pool.weight" &&
+1 -1
View File
@@ -1741,7 +1741,7 @@ std::string convert_tensor_name(std::string name, SDVersion version) {
const std::vector<std::pair<std::string, std::string>> connector_prefix_map = { const std::vector<std::pair<std::string, std::string>> connector_prefix_map = {
{"embeddings_connectors.text_encoders.", "text_encoders."}, {"embeddings_connectors.text_encoders.", "text_encoders."},
{"embeddings_connectors.", sd_version_is_minimax_h3_image(version) ? "text_encoders.ph3_image_connector." : ""}, {"embeddings_connectors.", sd_version_is_minimax_h3(version) ? "text_encoders.ph3_image_connector." : ""},
}; };
replace_with_prefix_map(name, connector_prefix_map); replace_with_prefix_map(name, connector_prefix_map);