Skip to content

Commit 1271488

Browse files
committed
feat(paged): P1 bf16-stream residual-segment executor + norm-bf16 kernels
Additive, default-off (LLAMA_BF16_STREAM=1) bf16-resident activation stream for the q36 residual path, targeting prefill bucket 3 (the convert/glue tax). - norm-bf16.cu/.cuh: rms_norm, the 0042 pre_add_mul, and the 0044 gate_mul norms templated on output dtype, bit-faithful to the f32 kernels up to the __float2bfloat16 store. - One additive clause in ggml_cuda_try_fuse detects a residual-stream norm-producer whose consumers are all large-M cuBLAS-bf16 projections, runs the norm into a bf16 pool buffer, executes the owned span inline through a bf16 view, then skips it. Strict all-consumers-are-ours guard keeps the f32 norm un-materialised and bails to the stock f32 path otherwise (small-M, decode, MMQ, native-FP4, multi-consumer). - LLAMA_BF16_CUBLAS_F32_OUT plank: owned projections write f32 directly from bf16 tensor-core compute, skipping the bf16 dst pool + bf16->f32 convert; the F32_OUT else-branch is byte-identical to the original cuBLAS path. Default md5 stays canonical with the code present-but-off and env-on (small-M prompts bail): MoE 8cb0ce23777bf55f92f63d0292c756b0, dense 5951a5b4d624ce891e22ab5fca9bc439. Assisted-by: Claude:opus-4.8 [Claude Code] Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
1 parent 1edddc8 commit 1271488

3 files changed

Lines changed: 793 additions & 24 deletions

File tree

ggml/src/ggml-cuda/ggml-cuda.cu

Lines changed: 273 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
#include "ggml-cuda/mmvf.cuh"
3737
#include "ggml-cuda/mmvq.cuh"
3838
#include "ggml-cuda/norm.cuh"
39+
#include "ggml-cuda/norm-bf16.cuh"
3940
#include "ggml-cuda/opt-step-adamw.cuh"
4041
#include "ggml-cuda/opt-step-sgd.cuh"
4142
#include "ggml-cuda/out-prod.cuh"
@@ -1628,12 +1629,29 @@ static const cublas_force_compute_type & ggml_cuda_cublas_get_force_compute_type
16281629
return compute_type;
16291630
}
16301631

1632+
// [P1 bf16-stream] LLAMA_BF16_CUBLAS_F32_OUT plank. When set (by the bf16-stream
1633+
// segment executor around an owned projection, or globally via the env), the cuBLAS
1634+
// bf16/nvfp4 GEMM writes f32 directly from the bf16 tensor-core compute, skipping the
1635+
// bf16 dst pool buffer + the bf16->f32 output convert_dtype. The result is the full
1636+
// f32 GEMM accumulation (the current path rounds it to bf16 then widens back), so this
1637+
// is a strictly-more-precise dtype change gated on the opt-in KL path, never md5.
1638+
static thread_local bool g_bf16_stream_f32_out = false;
1639+
static bool ggml_cuda_bf16_cublas_f32_out_env() {
1640+
static const bool e = [] {
1641+
const char * s = getenv("LLAMA_BF16_CUBLAS_F32_OUT");
1642+
return s != nullptr && atoi(s) != 0;
1643+
}();
1644+
return e;
1645+
}
1646+
16311647
static void ggml_cuda_op_mul_mat_cublas(
16321648
ggml_backend_cuda_context & ctx,
16331649
const ggml_tensor * src0, const ggml_tensor * src1, ggml_tensor * dst, const char * src0_dd_i, const float * src1_ddf_i,
16341650
const char * src1_ddq_i, float * dst_dd_i, const int64_t row_low, const int64_t row_high, const int64_t src1_ncols,
16351651
const int64_t src1_padded_row_size, cudaStream_t stream) {
16361652

1653+
const bool bf16_stream_f32_out = g_bf16_stream_f32_out || ggml_cuda_bf16_cublas_f32_out_env();
1654+
16371655
GGML_ASSERT(src0_dd_i != nullptr);
16381656
GGML_ASSERT(src1_ddf_i != nullptr);
16391657
GGML_ASSERT(dst_dd_i != nullptr);
@@ -1686,23 +1704,34 @@ static void ggml_cuda_op_mul_mat_cublas(
16861704
}
16871705
const nv_bfloat16 * src1_ptr = src1->type == GGML_TYPE_BF16 ? (const nv_bfloat16 *) src1_ddf_i : src1_as_bf16.get();
16881706
const nv_bfloat16 * src0_ptr = src0_as_bf16.get();
1689-
ggml_cuda_pool_alloc<nv_bfloat16> dst_bf16(ctx.pool(id), row_diff*src1_ncols);
16901707

16911708
const float alpha_f32 = 1.0f;
16921709
const float beta_f32 = 0.0f;
16931710

16941711
CUBLAS_CHECK(cublasSetStream(ctx.cublas_handle(id), stream));
1695-
CUBLAS_CHECK(
1696-
cublasGemmEx(ctx.cublas_handle(id), CUBLAS_OP_T, CUBLAS_OP_N,
1697-
row_diff, src1_ncols, ne10,
1698-
&alpha_f32, src0_ptr, CUDA_R_16BF, ne00,
1699-
src1_ptr, CUDA_R_16BF, ne10,
1700-
&beta_f32, dst_bf16.get(), CUDA_R_16BF, ldc,
1701-
CUBLAS_COMPUTE_32F,
1702-
CUBLAS_GEMM_DEFAULT_TENSOR_OP));
1703-
1704-
const to_fp32_cuda_t to_fp32_cuda = ggml_get_to_fp32_cuda(GGML_TYPE_BF16);
1705-
to_fp32_cuda(dst_bf16.get(), dst_dd_i, row_diff*src1_ncols, stream);
1712+
if (bf16_stream_f32_out) {
1713+
// [P1 bf16-stream] write f32 directly, skip the bf16 dst pool + convert.
1714+
CUBLAS_CHECK(
1715+
cublasGemmEx(ctx.cublas_handle(id), CUBLAS_OP_T, CUBLAS_OP_N,
1716+
row_diff, src1_ncols, ne10,
1717+
&alpha_f32, src0_ptr, CUDA_R_16BF, ne00,
1718+
src1_ptr, CUDA_R_16BF, ne10,
1719+
&beta_f32, dst_dd_i, CUDA_R_32F, ldc,
1720+
CUBLAS_COMPUTE_32F,
1721+
CUBLAS_GEMM_DEFAULT_TENSOR_OP));
1722+
} else {
1723+
ggml_cuda_pool_alloc<nv_bfloat16> dst_bf16(ctx.pool(id), row_diff*src1_ncols);
1724+
CUBLAS_CHECK(
1725+
cublasGemmEx(ctx.cublas_handle(id), CUBLAS_OP_T, CUBLAS_OP_N,
1726+
row_diff, src1_ncols, ne10,
1727+
&alpha_f32, src0_ptr, CUDA_R_16BF, ne00,
1728+
src1_ptr, CUDA_R_16BF, ne10,
1729+
&beta_f32, dst_bf16.get(), CUDA_R_16BF, ldc,
1730+
CUBLAS_COMPUTE_32F,
1731+
CUBLAS_GEMM_DEFAULT_TENSOR_OP));
1732+
const to_fp32_cuda_t to_fp32_cuda = ggml_get_to_fp32_cuda(GGML_TYPE_BF16);
1733+
to_fp32_cuda(dst_bf16.get(), dst_dd_i, row_diff*src1_ncols, stream);
1734+
}
17061735
} else if (supports_bf16 && src0->type == GGML_TYPE_BF16 && ggml_is_contiguous(src0) && row_diff == src0->ne[1]) {
17071736
ggml_cuda_pool_alloc<nv_bfloat16> src1_as_bf16(ctx.pool(id));
17081737
if (src1->type != GGML_TYPE_BF16) {
@@ -1714,23 +1743,34 @@ static void ggml_cuda_op_mul_mat_cublas(
17141743
}
17151744
const nv_bfloat16 * src1_ptr = src1->type == GGML_TYPE_BF16 ? (const nv_bfloat16 *) src1_ddf_i : src1_as_bf16.get();
17161745
const nv_bfloat16 * src0_ptr = (const nv_bfloat16 *)src0_dd_i;
1717-
ggml_cuda_pool_alloc<nv_bfloat16> dst_bf16(ctx.pool(id), row_diff*src1_ncols);
17181746

17191747
const float alpha_f32 = 1.0f;
17201748
const float beta_f32 = 0.0f;
17211749

17221750
CUBLAS_CHECK(cublasSetStream(ctx.cublas_handle(id), stream));
1723-
CUBLAS_CHECK(
1724-
cublasGemmEx(ctx.cublas_handle(id), CUBLAS_OP_T, CUBLAS_OP_N,
1725-
row_diff, src1_ncols, ne10,
1726-
&alpha_f32, src0_ptr, CUDA_R_16BF, ne00,
1727-
src1_ptr, CUDA_R_16BF, ne10,
1728-
&beta_f32, dst_bf16.get(), CUDA_R_16BF, ldc,
1729-
CUBLAS_COMPUTE_32F,
1730-
CUBLAS_GEMM_DEFAULT_TENSOR_OP));
1731-
1732-
const to_fp32_cuda_t to_fp32_cuda = ggml_get_to_fp32_cuda(GGML_TYPE_BF16);
1733-
to_fp32_cuda(dst_bf16.get(), dst_dd_i, row_diff*src1_ncols, stream);
1751+
if (bf16_stream_f32_out) {
1752+
// [P1 bf16-stream] write f32 directly, skip the bf16 dst pool + convert.
1753+
CUBLAS_CHECK(
1754+
cublasGemmEx(ctx.cublas_handle(id), CUBLAS_OP_T, CUBLAS_OP_N,
1755+
row_diff, src1_ncols, ne10,
1756+
&alpha_f32, src0_ptr, CUDA_R_16BF, ne00,
1757+
src1_ptr, CUDA_R_16BF, ne10,
1758+
&beta_f32, dst_dd_i, CUDA_R_32F, ldc,
1759+
CUBLAS_COMPUTE_32F,
1760+
CUBLAS_GEMM_DEFAULT_TENSOR_OP));
1761+
} else {
1762+
ggml_cuda_pool_alloc<nv_bfloat16> dst_bf16(ctx.pool(id), row_diff*src1_ncols);
1763+
CUBLAS_CHECK(
1764+
cublasGemmEx(ctx.cublas_handle(id), CUBLAS_OP_T, CUBLAS_OP_N,
1765+
row_diff, src1_ncols, ne10,
1766+
&alpha_f32, src0_ptr, CUDA_R_16BF, ne00,
1767+
src1_ptr, CUDA_R_16BF, ne10,
1768+
&beta_f32, dst_bf16.get(), CUDA_R_16BF, ldc,
1769+
CUBLAS_COMPUTE_32F,
1770+
CUBLAS_GEMM_DEFAULT_TENSOR_OP));
1771+
const to_fp32_cuda_t to_fp32_cuda = ggml_get_to_fp32_cuda(GGML_TYPE_BF16);
1772+
to_fp32_cuda(dst_bf16.get(), dst_dd_i, row_diff*src1_ncols, stream);
1773+
}
17341774
} else if (fast_fp16_hardware_available(cc) && use_fp16) {
17351775
// convert src0 and src1 to fp16, multiply as fp16, convert dst to fp32
17361776
ggml_cuda_pool_alloc<half> src0_as_f16(ctx.pool(id));
@@ -4706,6 +4746,215 @@ static int ggml_cuda_try_fuse(ggml_backend_cuda_context * cuda_ctx, ggml_cgraph
47064746
return 2;
47074747
}
47084748

4749+
// [P1 bf16-stream] Generalized additive segment executor (LLAMA_BF16_STREAM=1,
4750+
// default off). ONE clause; the residual-stream segment is detected inside it.
4751+
// Owns any norm-producer whose consumers are ALL large-M cuBLAS-bf16 projections and
4752+
// runs that norm into a bf16 pool buffer so every projection reads the bf16
4753+
// activation directly - no per-op f32->bf16 convert_dtype glue. Two live q36 kinds:
4754+
// * plain rms_norm+mul {RMS_NORM,MUL} -> BF16 q/k/v / GDN in_proj (may be
4755+
// multi-consumer: q,k,v share it)
4756+
// * 0044 gated-DeltaNet output norm {SILU,RMS_NORM,MUL,MUL} -> ssm_out (the P0 seg)
4757+
// (The 0042 {ADD,RMS_NORM,MUL} residual-fused norm is handled by its f32 clause below
4758+
// and, on q36, feeds the NVFP4-MMQ experts, so a bf16 stream there would bail; its
4759+
// bf16 variant lives in norm-bf16.cu for op-set completeness.)
4760+
//
4761+
// Correctness: strict all-consumers-are-ours guard - the f32 norm output is never
4762+
// materialised, so every node that transitively reads it must be one of our owned
4763+
// projections (as src1); any other reader, or an unrelated compute node inside the
4764+
// skipped span, bails and the f32 fused-norm path runs unchanged. Each projection is
4765+
// executed inline through a bf16 view of the shared buffer; the whole owned span
4766+
// (norm nodes + intervening pure-view no-ops + the projections) is then skipped. The
4767+
// LLAMA_BF16_CUBLAS_F32_OUT plank additionally makes the owned projections write f32
4768+
// directly (skipping the dst convert). Env-off path and decode/small-M md5 untouched.
4769+
static const bool bf16_stream = [] {
4770+
const char * e = getenv("LLAMA_BF16_STREAM");
4771+
return e != nullptr && atoi(e) != 0;
4772+
}();
4773+
static const int bf16_stream_trace = [] {
4774+
const char * e = getenv("LLAMA_BF16_STREAM_TRACE");
4775+
return e != nullptr ? atoi(e) : 0;
4776+
}();
4777+
static const bool bf16_stream_f32_out_default = [] {
4778+
const char * e = getenv("LLAMA_BF16_CUBLAS_F32_OUT");
4779+
return e == nullptr || atoi(e) != 0; // plank ON by default when a segment engages
4780+
}();
4781+
if (bf16_stream) {
4782+
// ---- detect the norm-producer kind + the f32 activation tensor + node span ----
4783+
int kind = 0; // 1=plain rms+mul, 2=gated-DeltaNet output norm
4784+
int norm_span = 0;
4785+
const char * seg_kind = nullptr;
4786+
ggml_tensor * k_rms = nullptr, * k_mul = nullptr, * k_silu = nullptr;
4787+
ggml_tensor * norm_out = nullptr;
4788+
if (ggml_cuda_can_fuse(cgraph, i, { GGML_OP_UNARY, GGML_OP_RMS_NORM, GGML_OP_MUL, GGML_OP_MUL }, { GGML_UNARY_OP_SILU })) {
4789+
kind = 2; k_silu = cgraph->nodes[i]; k_rms = cgraph->nodes[i + 1]; k_mul = cgraph->nodes[i + 2];
4790+
norm_out = cgraph->nodes[i + 3]; norm_span = 4; seg_kind = "gate_norm";
4791+
} else if (ggml_cuda_can_fuse(cgraph, i, { GGML_OP_RMS_NORM, GGML_OP_MUL }, {})) {
4792+
kind = 1; k_rms = cgraph->nodes[i]; k_mul = cgraph->nodes[i + 1];
4793+
norm_out = cgraph->nodes[i + 1]; norm_span = 2; seg_kind = "rms_norm";
4794+
}
4795+
4796+
if (kind != 0) {
4797+
const int cc = ggml_cuda_info().devices[ggml_cuda_get_device()].cc;
4798+
const int norm_end = i + norm_span;
4799+
4800+
// follow a pure view/reshape chain up to norm_out
4801+
auto roots_at = [](const ggml_tensor * t, const ggml_tensor * root) -> bool {
4802+
const ggml_tensor * c = t;
4803+
for (int d = 0; d < 8 && c != nullptr; ++d) {
4804+
if (c == root) return true;
4805+
if (c->view_src) { c = c->view_src; continue; }
4806+
if ((c->op == GGML_OP_RESHAPE || c->op == GGML_OP_VIEW || c->op == GGML_OP_PERMUTE ||
4807+
c->op == GGML_OP_TRANSPOSE || c->op == GGML_OP_CONT) && c->src[0]) { c = c->src[0]; continue; }
4808+
break;
4809+
}
4810+
return false;
4811+
};
4812+
// Metadata-only no-ops (match the stock capture loop's skip set). CONT is
4813+
// NOT here: it materializes a contiguous copy, so a CONT of norm_out must fall
4814+
// through to the roots_at check below and bail (it would need the f32 norm).
4815+
auto is_pure_view = [](const ggml_tensor * t) -> bool {
4816+
return t->op == GGML_OP_RESHAPE || t->op == GGML_OP_VIEW || t->op == GGML_OP_PERMUTE ||
4817+
t->op == GGML_OP_TRANSPOSE || t->op == GGML_OP_NONE;
4818+
};
4819+
// ownable large-M cuBLAS-bf16 projection whose src1 is the FULL norm output
4820+
auto is_owned_proj = [&](const ggml_tensor * p) -> bool {
4821+
if (p->op != GGML_OP_MUL_MAT) return false;
4822+
const ggml_tensor * w = p->src[0];
4823+
const ggml_tensor * x1 = p->src[1];
4824+
if (!w || !x1) return false;
4825+
if (!(x1 == norm_out || x1->view_src == norm_out ||
4826+
(x1->op == GGML_OP_RESHAPE && x1->src[0] == norm_out))) return false;
4827+
if (ggml_nelements(x1) != ggml_nelements(norm_out)) return false; // full, offset 0
4828+
return (w->type == GGML_TYPE_BF16 || w->type == GGML_TYPE_NVFP4) && ggml_is_contiguous(w) &&
4829+
p->type == GGML_TYPE_F32 &&
4830+
x1->ne[2] == 1 && x1->ne[3] == 1 &&
4831+
x1->ne[1] >= 128 &&
4832+
!ggml_cuda_fp4_prefill_should_engage(w, x1, const_cast<ggml_tensor *>(p), cc) &&
4833+
!ggml_cuda_should_use_mmq(w->type, cc, x1->ne[1], /*n_experts=*/0);
4834+
};
4835+
4836+
// Scan the rest of the graph: collect our projections, enforce that every
4837+
// consumer of norm_out is one of them, and that the skipped span holds only
4838+
// pure views / our projections.
4839+
bool ok = true;
4840+
int n_proj = 0;
4841+
int max_proj_idx = -1;
4842+
const char * miss_reason = "unknown";
4843+
int miss_node = -1;
4844+
const char * miss_op = "";
4845+
ggml_tensor * projs[16];
4846+
for (int j = norm_end; j < cgraph->n_nodes && ok; ++j) {
4847+
ggml_tensor * nj = cgraph->nodes[j];
4848+
// Pure view/reshape no-ops are part of the src1 view chain (or unrelated
4849+
// metadata ops): they carry no kernel and are re-expressed by the inline
4850+
// bf16 src1, so they never force f32 materialization. A *real* downstream
4851+
// consumer that reads norm_out through such a view is still caught below,
4852+
// because roots_at() climbs the view chain to norm_out.
4853+
if (is_pure_view(nj)) {
4854+
continue;
4855+
}
4856+
if (is_owned_proj(nj)) {
4857+
if (n_proj < 16) { projs[n_proj] = nj; }
4858+
n_proj++;
4859+
max_proj_idx = j;
4860+
continue;
4861+
}
4862+
// any (non-view, non-projection) reader of norm_out disqualifies the segment
4863+
for (int s = 0; s < GGML_MAX_SRC; ++s) {
4864+
if (nj->src[s] && roots_at(nj->src[s], norm_out)) {
4865+
ok = false; miss_reason = "nonproj_consumer"; miss_node = j; miss_op = ggml_op_name(nj->op); break;
4866+
}
4867+
}
4868+
}
4869+
// require projections, room in the fixed buffer, and a bounded span. The
4870+
// span [norm_end, max_proj_idx] may hold non-projection compute (q36 QK-norm /
4871+
// scale on the projection outputs); those never read norm_out (enforced above)
4872+
// so the whole span is executed inline in graph order below - owned projections
4873+
// through the bf16 buffer, everything else via the stock per-node executor -
4874+
// and then skipped as one unit.
4875+
const int span_len = max_proj_idx - norm_end;
4876+
if (!(n_proj >= 1 && n_proj <= 16 && span_len <= 96)) {
4877+
if (ok) { miss_reason = (n_proj == 0) ? "no_owned_proj" : (n_proj > 16 ? "too_many_proj" : "span_too_long"); }
4878+
ok = false;
4879+
}
4880+
4881+
if (ok) {
4882+
const int64_t ne_tot = ggml_nelements(norm_out);
4883+
ggml_cuda_pool_alloc<nv_bfloat16> norm_bf16(cuda_ctx->pool(), ne_tot);
4884+
if (kind == 2) {
4885+
ggml_cuda_rms_norm_gate_mul_bf16out(*cuda_ctx, k_rms, k_mul, k_silu, norm_out, norm_bf16.get());
4886+
} else {
4887+
ggml_cuda_rms_norm_mul_bf16out(*cuda_ctx, k_rms, k_mul, norm_bf16.get());
4888+
}
4889+
4890+
// Execute the whole owned span inline, in graph order (mirrors the stock
4891+
// capture loop's per-node handling for the non-owned nodes).
4892+
for (int j = norm_end; j <= max_proj_idx; ++j) {
4893+
ggml_tensor * nj = cgraph->nodes[j];
4894+
4895+
bool mine = false;
4896+
for (int p = 0; p < n_proj; ++p) { if (projs[p] == nj) { mine = true; break; } }
4897+
4898+
if (mine) {
4899+
ggml_tensor * proj_src1 = nj->src[1];
4900+
ggml_tensor src1_bf16 = *proj_src1;
4901+
src1_bf16.type = GGML_TYPE_BF16;
4902+
src1_bf16.data = norm_bf16.get();
4903+
src1_bf16.view_src = nullptr;
4904+
src1_bf16.view_offs = 0;
4905+
src1_bf16.nb[0] = sizeof(nv_bfloat16);
4906+
src1_bf16.nb[1] = src1_bf16.nb[0] * src1_bf16.ne[0];
4907+
src1_bf16.nb[2] = src1_bf16.nb[1] * src1_bf16.ne[1];
4908+
src1_bf16.nb[3] = src1_bf16.nb[2] * src1_bf16.ne[2];
4909+
4910+
ggml_tensor * saved_src1 = nj->src[1];
4911+
nj->src[1] = &src1_bf16;
4912+
g_bf16_stream_f32_out = bf16_stream_f32_out_default;
4913+
const bool okc = ggml_cuda_compute_forward(*cuda_ctx, nj);
4914+
g_bf16_stream_f32_out = false;
4915+
nj->src[1] = saved_src1;
4916+
GGML_ASSERT(okc);
4917+
continue;
4918+
}
4919+
4920+
// non-owned span node: mirror the stock loop (skip metadata no-ops,
4921+
// run the rest through the per-node executor)
4922+
if (ggml_is_empty(nj) || nj->op == GGML_OP_RESHAPE || nj->op == GGML_OP_TRANSPOSE ||
4923+
nj->op == GGML_OP_VIEW || nj->op == GGML_OP_PERMUTE || nj->op == GGML_OP_NONE) {
4924+
continue;
4925+
}
4926+
if ((nj->flags & GGML_TENSOR_FLAG_COMPUTE) == 0) {
4927+
continue;
4928+
}
4929+
const bool okn = ggml_cuda_compute_forward(*cuda_ctx, nj);
4930+
GGML_ASSERT(okn);
4931+
}
4932+
4933+
static std::atomic<int> bf16_stream_engage_count{0};
4934+
const int ec = bf16_stream_engage_count.fetch_add(1, std::memory_order_relaxed);
4935+
if (bf16_stream_trace > 0 && ec < bf16_stream_trace) {
4936+
const ggml_tensor * w0 = projs[0]->src[0];
4937+
fprintf(stderr,
4938+
"[LLAMA_BF16_STREAM] engaged seg=%s node=%d n_proj=%d last_proj=%d "
4939+
"M=%" PRId64 " N=%" PRId64 " K=%" PRId64 " f32out=%d skip=%d\n",
4940+
seg_kind, i, n_proj, max_proj_idx, projs[0]->src[1]->ne[1], w0->ne[1], w0->ne[0],
4941+
bf16_stream_f32_out_default ? 1 : 0, max_proj_idx - i);
4942+
}
4943+
return max_proj_idx - i; // skip norm nodes + intervening views + all owned projections
4944+
}
4945+
4946+
if (bf16_stream_trace > 0) {
4947+
static std::atomic<int> bf16_stream_miss_count{0};
4948+
const int mc = bf16_stream_miss_count.fetch_add(1, std::memory_order_relaxed);
4949+
if (mc < bf16_stream_trace) {
4950+
fprintf(stderr,
4951+
"[LLAMA_BF16_STREAM] miss seg=%s node=%d n_proj=%d reason=%s miss_node=%d miss_op=%s\n",
4952+
seg_kind, i, n_proj, miss_reason, miss_node, miss_op);
4953+
}
4954+
}
4955+
}
4956+
}
4957+
47094958
// Fused gated RMS norm: RMS norm + weight multiply + SiLU-gated multiply
47104959
// (bit-exact). The Qwen3.6 gated-DeltaNet output norm. Default ON; set
47114960
// LLAMA_FUSE_GATE_RMSNORM=0 for a clean A/B against the unfused path.

0 commit comments

Comments
 (0)