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+
16311647static 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