Skip to content

Commit 8a9cb75

Browse files
IIIIIllllIIIIIlllll0xShug0
authored andcommitted
Breeze encoder chunked vram (#431)
* breeze: chunk the speech-encoder conv stack to bound clone VRAM The encoder graph was built at the exact reference-audio length, so conv activations grew linearly (~45 MiB/s of reference) and every new length triggered a full graph rebuild; a 60 s reference cost ~2.5 GB extra over a 6 s one. Split the encoder into two graphs. The conv stack now runs on fixed 5 s chunks (120000 samples) preceded by a 9600-sample left overlap that covers the stack's exact 5240-sample receptive field; chunk lengths are multiples of the 960x transformer stride, so no per-stage right padding occurs and the discarded overlap frames absorb the zero left pads that represent audio start in the first chunk. Stitched outputs are bit-identical to a single-pass encode of the same input (verified over 68 frames x 16 codebooks). The transformer, downsample, and projections run once over the full frame sequence at frame scale, where even minute-long references cost only tens of MiB. Measured on a 2080 Ti (Vulkan, native q8_0 GGUF, peak minus idle baseline): the VRAM slope over reference length drops from ~45 MiB/s to ~11 MiB/s (remaining slope is the frame-scale transformer graph and the longer AR prefill from reference codes), and a 60 s reference peaks ~1.4 GB lower. Encode time for 60 s improves from 3561 ms to 2197 ms. * breeze: bucket speech-encoder transformer graph capacity The transformer graph was rebuilt at the exact frame count for every distinct reference length. Round the capacity up to 125-frame (5 s) buckets so lengths within a bucket share one graph. Unused bucket frames are replicate-padded to match the downsample conv's Replicate right pad; causal attention keeps padding frames invisible to real frames. Verified bit-identical reference codes vs exact-length graphs at 6 s and 15 s; odd lengths show sub-1% last-frame diffs from flash-attention tiling, the same accepted noise class as the pre-existing length sensitivity. Single-run peak VRAM is unchanged. * ggml-vulkan, breeze: fused round-to-bf16 unary op on Vulkan Vulkan previously paid a cast round trip (f32->bf16->f32, two kernels, a bf16 intermediate tensor) at every activation-rounding point of the breeze decoder. Add a round_bf16 compute shader (f32/f16/bf16 in, always f32 out, round-to-nearest-even via the same fp32_to_bf16 bit trick the cpy shaders use), register pipelines indexed by source type, handle the widened f32 dst in the unary pipeline selection and op-support checks, and enable fused_round for Vulkan in the breeze activation-cast policy. Verified bit-identical breeze reference codes vs the cast round trip at 6 s and 15 s references. Peak VRAM on a 2080 Ti drops ~250 MiB at a 60 s reference (5491 -> 5239 MiB); no measurable change at 6 s. * ggml-vulkan: handle row-strided inputs in fused round-to-bf16 The breeze activation-rounding policy admits row-strided views into ggml_round_bf16 (ggml_is_contiguous_rows gate in qwen_decoder). The Vulkan port dispatched every input to the flat shader, which indexes the source as a contiguous array, so row-strided views read garbage and clone output degenerated into noise. Route non-contiguous inputs to a new round_bf16_strided shader built on generic_unary_head (same pattern as sigmoid_strided), keeping the flat fast path for contiguous inputs.
1 parent ff38f15 commit 8a9cb75

7 files changed

Lines changed: 319 additions & 51 deletions

File tree

external/ggml/src/ggml-vulkan/ggml-vulkan.cpp

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -811,6 +811,8 @@ struct vk_device_struct {
811811
vk_pipeline pipeline_softplus[2];
812812
vk_pipeline pipeline_step[2];
813813
vk_pipeline pipeline_round[2];
814+
vk_pipeline pipeline_round_bf16[3];
815+
vk_pipeline pipeline_round_bf16_strided[3];
814816
vk_pipeline pipeline_ceil[2];
815817
vk_pipeline pipeline_floor[2];
816818
vk_pipeline pipeline_trunc[2];
@@ -4750,6 +4752,15 @@ static void ggml_vk_load_shaders(vk_device& device) {
47504752
CREATE_UNARY(exp)
47514753
#undef CREATE_UNARY
47524754

4755+
// round-to-bf16: f32/f16/bf16 in, always f32 out (index by src type).
4756+
ggml_vk_create_pipeline(device, device->pipeline_round_bf16[0], "round_bf16_f32", round_bf16_f32_len, round_bf16_f32_data, "main", 2, sizeof(vk_op_push_constants), {512, 1, 1}, {}, 1);
4757+
ggml_vk_create_pipeline(device, device->pipeline_round_bf16[1], "round_bf16_f16", round_bf16_f16_len, round_bf16_f16_data, "main", 2, sizeof(vk_op_push_constants), {512, 1, 1}, {}, 1);
4758+
ggml_vk_create_pipeline(device, device->pipeline_round_bf16[2], "round_bf16_bf16", round_bf16_bf16_len, round_bf16_bf16_data, "main", 2, sizeof(vk_op_push_constants), {512, 1, 1}, {}, 1);
4759+
// strided variant for non-contiguous (e.g. row-strided view) inputs.
4760+
ggml_vk_create_pipeline(device, device->pipeline_round_bf16_strided[0], "round_bf16_strided_f32", round_bf16_strided_f32_len, round_bf16_strided_f32_data, "main", 2, sizeof(vk_op_unary_push_constants), {512, 1, 1}, {}, 1);
4761+
ggml_vk_create_pipeline(device, device->pipeline_round_bf16_strided[1], "round_bf16_strided_f16", round_bf16_strided_f16_len, round_bf16_strided_f16_data, "main", 2, sizeof(vk_op_unary_push_constants), {512, 1, 1}, {}, 1);
4762+
ggml_vk_create_pipeline(device, device->pipeline_round_bf16_strided[2], "round_bf16_strided_bf16", round_bf16_strided_bf16_len, round_bf16_strided_bf16_data, "main", 2, sizeof(vk_op_unary_push_constants), {512, 1, 1}, {}, 1);
4763+
47534764
ggml_vk_create_pipeline(device, device->pipeline_add1_f16_f16, "add1_f16_f16", add1_f16_f16_len, add1_f16_f16_data, "main", 3, sizeof(vk_op_binary_push_constants), {512, 1, 1}, {}, 1);
47544765
ggml_vk_create_pipeline(device, device->pipeline_add1_f16_f32, "add1_f16_f32", add1_f16_f32_len, add1_f16_f32_data, "main", 3, sizeof(vk_op_binary_push_constants), {512, 1, 1}, {}, 1);
47554766
ggml_vk_create_pipeline(device, device->pipeline_add1_f32_f32, "add1_f32_f32", add1_f32_f32_len, add1_f32_f32_data, "main", 3, sizeof(vk_op_binary_push_constants), {512, 1, 1}, {}, 1);
@@ -9740,6 +9751,19 @@ static vk_pipeline ggml_vk_op_get_pipeline(ggml_backend_vk_context * ctx, const
97409751
}
97419752
return nullptr;
97429753
case GGML_OP_UNARY:
9754+
// ROUND_BF16 widens to f32: src may be f32/f16/bf16 while dst is f32.
9755+
if (ggml_get_unary_op(dst) == GGML_UNARY_OP_ROUND_BF16) {
9756+
if (dst->type != GGML_TYPE_F32) {
9757+
return nullptr;
9758+
}
9759+
const bool strided = !ggml_is_contiguous(src0) || !ggml_is_contiguous(dst);
9760+
switch (src0->type) {
9761+
case GGML_TYPE_F32: return strided ? ctx->device->pipeline_round_bf16_strided[0] : ctx->device->pipeline_round_bf16[0];
9762+
case GGML_TYPE_F16: return strided ? ctx->device->pipeline_round_bf16_strided[1] : ctx->device->pipeline_round_bf16[1];
9763+
case GGML_TYPE_BF16: return strided ? ctx->device->pipeline_round_bf16_strided[2] : ctx->device->pipeline_round_bf16[2];
9764+
default: return nullptr;
9765+
}
9766+
}
97439767
if ((src0->type != GGML_TYPE_F32 && src0->type != GGML_TYPE_F16) ||
97449768
(dst->type != GGML_TYPE_F32 && dst->type != GGML_TYPE_F16) ||
97459769
(src0->type != dst->type)) {
@@ -11481,6 +11505,11 @@ static void ggml_vk_sigmoid_strided(ggml_backend_vk_context * ctx, vk_context& s
1148111505
ggml_vk_op_f32(ctx, subctx, src0, nullptr, nullptr, nullptr, dst, GGML_OP_UNARY, std::move(p));
1148211506
}
1148311507

11508+
static void ggml_vk_round_bf16_strided(ggml_backend_vk_context * ctx, vk_context& subctx, const ggml_tensor * src0, ggml_tensor * dst) {
11509+
vk_op_unary_push_constants p = vk_op_unary_push_constants_init(src0, dst);
11510+
ggml_vk_op_f32(ctx, subctx, src0, nullptr, nullptr, nullptr, dst, GGML_OP_UNARY, std::move(p));
11511+
}
11512+
1148411513
static void ggml_vk_xielu(ggml_backend_vk_context * ctx, vk_context& subctx, const ggml_tensor * src0, ggml_tensor * dst) {
1148511514
float * op_params = (float *)dst->op_params;
1148611515
ggml_vk_op_f32<vk_op_push_constants>(ctx, subctx, src0, nullptr, nullptr, nullptr, dst, GGML_OP_UNARY,
@@ -13522,6 +13551,13 @@ static bool ggml_vk_build_graph(ggml_backend_vk_context * ctx, ggml_cgraph * cgr
1352213551
case GGML_UNARY_OP_SGN:
1352313552
ggml_vk_unary(ctx, compute_ctx, src0, node);
1352413553
break;
13554+
case GGML_UNARY_OP_ROUND_BF16:
13555+
if (!ggml_is_contiguous(src0) || !ggml_is_contiguous(node)) {
13556+
ggml_vk_round_bf16_strided(ctx, compute_ctx, src0, node);
13557+
break;
13558+
}
13559+
ggml_vk_unary(ctx, compute_ctx, src0, node);
13560+
break;
1352513561
case GGML_UNARY_OP_SIGMOID:
1352613562
if (!ggml_is_contiguous(src0) || !ggml_is_contiguous(node)) {
1352713563
ggml_vk_sigmoid_strided(ctx, compute_ctx, src0, node);
@@ -15772,6 +15808,9 @@ static bool ggml_backend_vk_device_supports_op(ggml_backend_dev_t dev, const ggm
1577215808
(op->src[0]->type == GGML_TYPE_F32 || op->src[0]->type == GGML_TYPE_F16) &&
1577315809
(op->type == GGML_TYPE_F32 || op->type == GGML_TYPE_F16) &&
1577415810
(op->src[0]->type == op->type);
15811+
case GGML_UNARY_OP_ROUND_BF16:
15812+
return (op->src[0]->type == GGML_TYPE_F32 || op->src[0]->type == GGML_TYPE_F16 || op->src[0]->type == GGML_TYPE_BF16) &&
15813+
(op->type == GGML_TYPE_F32);
1577515814
case GGML_UNARY_OP_SIGMOID:
1577615815
return (op->src[0]->type == GGML_TYPE_F32 || op->src[0]->type == GGML_TYPE_F16) &&
1577715816
(op->type == GGML_TYPE_F32 || op->type == GGML_TYPE_F16) &&
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
#version 450
2+
3+
#include "generic_head.glsl"
4+
#include "types.glsl"
5+
6+
layout(local_size_x = 512, local_size_y = 1, local_size_z = 1) in;
7+
8+
layout (binding = 0) readonly buffer X {A_TYPE data_a[];};
9+
layout (binding = 1) writeonly buffer D {D_TYPE data_d[];};
10+
11+
void main() {
12+
const uint i = gl_GlobalInvocationID.z * 262144 + gl_GlobalInvocationID.y * 512 + gl_GlobalInvocationID.x;
13+
14+
if (i >= p.KX) {
15+
return;
16+
}
17+
18+
#if defined(DATA_A_BF16)
19+
const float x = bf16_to_fp32(uint32_t(data_a[i]));
20+
#else
21+
const float x = float(data_a[i]);
22+
#endif
23+
// Round to bf16 precision and widen back to f32, matching the
24+
// f32 -> bf16 -> f32 cast round trip.
25+
data_d[i] = D_TYPE(bf16_to_fp32(fp32_to_bf16(x)));
26+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
#version 450
2+
3+
#include "types.glsl"
4+
#include "generic_unary_head.glsl"
5+
6+
layout(local_size_x = 512, local_size_y = 1, local_size_z = 1) in;
7+
8+
void main() {
9+
const uint idx = get_idx();
10+
11+
if (idx >= p.ne) {
12+
return;
13+
}
14+
15+
#if defined(DATA_A_BF16)
16+
const float x = bf16_to_fp32(uint32_t(data_a[get_aoffset() + src0_idx(idx)]));
17+
#else
18+
const float x = float(data_a[get_aoffset() + src0_idx(idx)]);
19+
#endif
20+
// Round to bf16 precision and widen back to f32, matching the
21+
// f32 -> bf16 -> f32 cast round trip.
22+
data_d[get_doffset() + dst_idx(idx)] = D_TYPE(bf16_to_fp32(fp32_to_bf16(x)));
23+
}

external/ggml/src/ggml-vulkan/vulkan-shaders/vulkan-shaders-gen.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -880,6 +880,12 @@ void process_shaders() {
880880
string_to_spv("step_f32", "step.comp", {{"A_TYPE", "float"}, {"D_TYPE", "float"}});
881881
string_to_spv("round_f16", "round.comp", {{"A_TYPE", "float16_t"}, {"D_TYPE", "float16_t"}});
882882
string_to_spv("round_f32", "round.comp", {{"A_TYPE", "float"}, {"D_TYPE", "float"}});
883+
string_to_spv("round_bf16_f32", "round_bf16.comp", {{"A_TYPE", "float"}, {"D_TYPE", "float"}});
884+
string_to_spv("round_bf16_f16", "round_bf16.comp", {{"A_TYPE", "float16_t"}, {"D_TYPE", "float"}});
885+
string_to_spv("round_bf16_bf16", "round_bf16.comp", {{"A_TYPE", "uint16_t"}, {"D_TYPE", "float"}, {"DATA_A_BF16", "1"}});
886+
string_to_spv("round_bf16_strided_f32", "round_bf16_strided.comp", {{"A_TYPE", "float"}, {"D_TYPE", "float"}});
887+
string_to_spv("round_bf16_strided_f16", "round_bf16_strided.comp", {{"A_TYPE", "float16_t"}, {"D_TYPE", "float"}});
888+
string_to_spv("round_bf16_strided_bf16", "round_bf16_strided.comp", {{"A_TYPE", "uint16_t"}, {"D_TYPE", "float"}, {"DATA_A_BF16", "1"}});
883889
string_to_spv("ceil_f16", "ceil.comp", {{"A_TYPE", "float16_t"}, {"D_TYPE", "float16_t"}});
884890
string_to_spv("ceil_f32", "ceil.comp", {{"A_TYPE", "float"}, {"D_TYPE", "float"}});
885891
string_to_spv("floor_f16", "floor.comp", {{"A_TYPE", "float16_t"}, {"D_TYPE", "float16_t"}});

include/engine/models/breeze_tts/speech_encoder.h

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@ namespace engine::models {
1919
namespace breeze_tts {
2020

2121
struct BreezeSpeechEncoderWeights;
22-
class BreezeSpeechEncoderGraph;
22+
class BreezeSpeechEncoderConvGraph;
23+
class BreezeSpeechEncoderTransformerGraph;
2324

2425
struct BreezeSpeechEncoderOutput {
2526
BreezeSpeechCodes codes;
@@ -46,7 +47,8 @@ class BreezeSpeechEncoderRuntime {
4647
core::ExecutionContext * execution_context_ = nullptr;
4748
size_t graph_arena_bytes_ = 0;
4849
std::unique_ptr<core::ConstantTensorCache> constants_;
49-
mutable std::unique_ptr<BreezeSpeechEncoderGraph> graph_;
50+
mutable std::unique_ptr<BreezeSpeechEncoderConvGraph> conv_graph_;
51+
mutable std::unique_ptr<BreezeSpeechEncoderTransformerGraph> transformer_graph_;
5052
};
5153

5254
} // namespace breeze_tts

src/models/breeze_tts/generator.cpp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -60,9 +60,9 @@ modules::QwenDecoderActivationCastPolicy breeze_bf16_activation_policy(core::Bac
6060
}
6161
policy.enabled = true;
6262
policy.type = GGML_TYPE_BF16;
63-
// CUDA/HIP implement the fused round-to-bf16 unary op; Vulkan does not and
64-
// keeps the cast round trip.
65-
policy.fused_round = backend_type == core::BackendType::Cuda || backend_type == core::BackendType::Hip;
63+
// CUDA/HIP/Vulkan implement the fused round-to-bf16 unary op.
64+
policy.fused_round = backend_type == core::BackendType::Cuda || backend_type == core::BackendType::Hip ||
65+
backend_type == core::BackendType::Vulkan;
6666
policy.after_input_norm = true;
6767
policy.after_qkv_projection = true;
6868
policy.after_qk_norm = true;

0 commit comments

Comments
 (0)