mirror of
https://github.com/leejet/stable-diffusion.cpp.git
synced 2026-11-11 11:26:34 +00:00
feat: add configurable attention chunk budget for MiniMax-H3 (#2292)
This commit is contained in:
1 parent
7bcd4ce8ce
commit
de00bafc3d
3 files changed
+65
-5
No files matched your search
@@ -155,5 +155,17 @@ Notes for this mode:
|
|||||||
- Width and height are aligned upward to a multiple of 32.
|
- Width and height are aligned upward to a multiple of 32.
|
||||||
- Frame count is aligned upward to the `17k + 5` grid, with a minimum of 5.
|
- Frame count is aligned upward to the `17k + 5` grid, with a minimum of 5.
|
||||||
- MiniMax-H3 runs at 24 fps; another requested value is overridden.
|
- MiniMax-H3 runs at 24 fps; another requested value is overridden.
|
||||||
|
- Attention head grouping is controlled by
|
||||||
|
`--model-args minimax_h3_attn_chunk_budget=2`, in GiB (default: `2`).
|
||||||
|
The budget estimates the size of a single FP32 Q/K tensor per group:
|
||||||
|
`head_dim * tokens * batch * 4 bytes * heads_per_group`. The group size adapts
|
||||||
|
to the sequence length, with a minimum of one head; inputs that fit use all
|
||||||
|
heads together. Set `0` to disable grouping, or a smaller budget such as
|
||||||
|
`0.25` to reduce peak activation memory for long sequences at the cost of
|
||||||
|
extra launches and concatenation. Each head still attends to the full sequence.
|
||||||
|
This is a grouping target, not a cap on total attention memory or VRAM usage;
|
||||||
|
other intermediates, backend workspace, and weights require additional memory.
|
||||||
|
`--max-vram` and `--offload-to-cpu` control weight residency, but the active
|
||||||
|
block's working memory must also fit within the VRAM budget.
|
||||||
- The default video flow shift is 12. The audio stream is mapped internally to
|
- The default video flow shift is 12. The audio stream is mapped internally to
|
||||||
its shift of 3, so the regular samplers can operate on the packed AV latent.
|
its shift of 3, so the regular samplers can operate on the packed AV latent.
|
||||||
@@ -20,6 +20,7 @@
|
|||||||
namespace MiniMaxH3 {
|
namespace MiniMaxH3 {
|
||||||
|
|
||||||
constexpr int H3_GRAPH_SIZE = 131072;
|
constexpr int H3_GRAPH_SIZE = 131072;
|
||||||
|
constexpr float DEFAULT_ATTN_CHUNK_BUDGET_GIB = 2.f;
|
||||||
constexpr float FRAME_RESCALE = 5.f / 3.f;
|
constexpr float FRAME_RESCALE = 5.f / 3.f;
|
||||||
constexpr float VISUAL_COND_TIMESTEP = 0.999f;
|
constexpr float VISUAL_COND_TIMESTEP = 0.999f;
|
||||||
|
|
||||||
@@ -231,6 +232,7 @@ namespace MiniMaxH3 {
|
|||||||
struct Attention : public GGMLBlock {
|
struct Attention : public GGMLBlock {
|
||||||
int64_t heads;
|
int64_t heads;
|
||||||
int64_t head_dim;
|
int64_t head_dim;
|
||||||
|
float head_chunk_budget_gib = DEFAULT_ATTN_CHUNK_BUDGET_GIB;
|
||||||
|
|
||||||
Attention(int64_t hidden_size,
|
Attention(int64_t hidden_size,
|
||||||
int64_t heads,
|
int64_t heads,
|
||||||
@@ -315,6 +317,31 @@ namespace MiniMaxH3 {
|
|||||||
ggml_tensor* attend(GGMLRunnerContext* ctx,
|
ggml_tensor* attend(GGMLRunnerContext* ctx,
|
||||||
const std::vector<ggml_tensor*>& qkv,
|
const std::vector<ggml_tensor*>& qkv,
|
||||||
ggml_tensor* pe = nullptr) {
|
ggml_tensor* pe = nullptr) {
|
||||||
|
// Budget one F32 Q/K tensor per group, not the full attention working set.
|
||||||
|
const double head_bytes = double(ggml_row_size(GGML_TYPE_F32, head_dim)) * qkv[0]->ne[2] * qkv[0]->ne[3];
|
||||||
|
const int64_t head_chunk = head_chunk_budget_gib > 0
|
||||||
|
? static_cast<int64_t>(std::clamp(double(head_chunk_budget_gib) * (1024ULL * 1024 * 1024) / head_bytes,
|
||||||
|
1.0, double(heads)))
|
||||||
|
: heads;
|
||||||
|
if (head_chunk < heads) {
|
||||||
|
std::vector<ggml_tensor*> outputs;
|
||||||
|
for (int64_t start = 0; start < heads; start += head_chunk) {
|
||||||
|
std::vector<ggml_tensor*> part;
|
||||||
|
for (auto tensor : qkv) {
|
||||||
|
part.push_back(ggml_ext_slice(ctx->ggml_ctx, tensor, 1, start, std::min(start + head_chunk, heads), false));
|
||||||
|
}
|
||||||
|
outputs.push_back(attend_heads(ctx, part, pe));
|
||||||
|
// Keep each head group's temporaries out of the concatenation tree's live set.
|
||||||
|
ctx->expand_graph(outputs.back());
|
||||||
|
}
|
||||||
|
return ggml_ext_vec_concat(ctx->ggml_ctx, outputs, 0);
|
||||||
|
}
|
||||||
|
return attend_heads(ctx, qkv, pe);
|
||||||
|
}
|
||||||
|
|
||||||
|
ggml_tensor* attend_heads(GGMLRunnerContext* ctx,
|
||||||
|
const std::vector<ggml_tensor*>& qkv,
|
||||||
|
ggml_tensor* pe) {
|
||||||
auto q_norm = std::dynamic_pointer_cast<RMSNorm>(blocks["q_norm"]);
|
auto q_norm = std::dynamic_pointer_cast<RMSNorm>(blocks["q_norm"]);
|
||||||
auto k_norm = std::dynamic_pointer_cast<RMSNorm>(blocks["k_norm"]);
|
auto k_norm = std::dynamic_pointer_cast<RMSNorm>(blocks["k_norm"]);
|
||||||
auto q = qkv[0];
|
auto q = qkv[0];
|
||||||
@@ -333,7 +360,7 @@ namespace MiniMaxH3 {
|
|||||||
q,
|
q,
|
||||||
k,
|
k,
|
||||||
v,
|
v,
|
||||||
static_cast<int>(heads),
|
static_cast<int>(qkv[0]->ne[1]),
|
||||||
nullptr,
|
nullptr,
|
||||||
true,
|
true,
|
||||||
ctx->flash_attn_enabled,
|
ctx->flash_attn_enabled,
|
||||||
@@ -1035,10 +1062,30 @@ namespace MiniMaxH3 {
|
|||||||
MiniMaxH3Runner(ggml_backend_t backend,
|
MiniMaxH3Runner(ggml_backend_t backend,
|
||||||
const String2TensorStorage& tensors,
|
const String2TensorStorage& tensors,
|
||||||
const std::string& prefix = "model.diffusion_model",
|
const std::string& prefix = "model.diffusion_model",
|
||||||
std::shared_ptr<RunnerWeightManager> weight_manager = nullptr)
|
std::shared_ptr<RunnerWeightManager> weight_manager = nullptr,
|
||||||
|
const char* model_args = nullptr)
|
||||||
: DiffusionModelRunner(backend, prefix, weight_manager),
|
: DiffusionModelRunner(backend, prefix, weight_manager),
|
||||||
config(Config::detect_from_weights(tensors, prefix)),
|
config(Config::detect_from_weights(tensors, prefix)),
|
||||||
model(config) {
|
model(config) {
|
||||||
|
float head_chunk_budget_gib = DEFAULT_ATTN_CHUNK_BUDGET_GIB;
|
||||||
|
for (const auto& [key, value] : parse_key_value_args(model_args, "model arg")) {
|
||||||
|
if (key == "minimax_h3_attn_chunk_budget") {
|
||||||
|
float parsed = 0.f;
|
||||||
|
if (parse_strict_float(value, parsed) && std::isfinite(parsed) && parsed >= 0.f) {
|
||||||
|
head_chunk_budget_gib = parsed;
|
||||||
|
} else {
|
||||||
|
LOG_WARN("ignoring invalid MiniMax-H3 model arg '%s=%s': expected a finite non-negative GiB budget", key.c_str(), value.c_str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
std::vector<GGMLBlock*> blocks;
|
||||||
|
model.get_all_blocks(blocks);
|
||||||
|
for (auto block : blocks) {
|
||||||
|
if (auto attention = dynamic_cast<Attention*>(block)) {
|
||||||
|
attention->head_chunk_budget_gib = head_chunk_budget_gib;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
LOG_INFO("MiniMax-H3 attention chunk budget: %.3g GiB (0 disables grouping)", head_chunk_budget_gib);
|
||||||
model.init(params_ctx, tensors, prefix);
|
model.init(params_ctx, tensors, prefix);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1178,7 +1225,8 @@ namespace MiniMaxH3 {
|
|||||||
timestep_features = make_input(timestep_feature_input_cache);
|
timestep_features = make_input(timestep_feature_input_cache);
|
||||||
}
|
}
|
||||||
|
|
||||||
auto runner_ctx = get_context();
|
auto graph = new_graph_custom(H3_GRAPH_SIZE);
|
||||||
|
auto runner_ctx = get_context(graph);
|
||||||
auto output = model.forward(&runner_ctx,
|
auto output = model.forward(&runner_ctx,
|
||||||
video,
|
video,
|
||||||
audio,
|
audio,
|
||||||
@@ -1201,7 +1249,6 @@ namespace MiniMaxH3 {
|
|||||||
output.second,
|
output.second,
|
||||||
1.f + (audio_scale - 1.f) * sigma_a));
|
1.f + (audio_scale - 1.f) * sigma_a));
|
||||||
auto merged = merge_av_latents(compute_ctx, output.first, output.second);
|
auto merged = merge_av_latents(compute_ctx, output.first, output.second);
|
||||||
auto graph = new_graph_custom(H3_GRAPH_SIZE);
|
|
||||||
ggml_build_forward_expand(graph, merged);
|
ggml_build_forward_expand(graph, merged);
|
||||||
return graph;
|
return graph;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,7 +18,8 @@ static bool build_minimax_h3(const sd::model_builders::Context& ctx, const Token
|
|||||||
result.diffusion = std::make_shared<MiniMaxH3::MiniMaxH3Runner>(ctx.backends.runtime_backend(SDBackendModule::DIFFUSION),
|
result.diffusion = std::make_shared<MiniMaxH3::MiniMaxH3Runner>(ctx.backends.runtime_backend(SDBackendModule::DIFFUSION),
|
||||||
tensor_storage_map,
|
tensor_storage_map,
|
||||||
"model.diffusion_model",
|
"model.diffusion_model",
|
||||||
weight_manager);
|
weight_manager,
|
||||||
|
sd_ctx_params->model_args);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user