Skip to content

[CPU] Add DisableBF16CompCumSumSinGen transformation - #37274

Open
mangguo321 wants to merge 8 commits into
openvinotoolkit:masterfrom
mangguo321:mang/kokoro_accuracy_fix
Open

[CPU] Add DisableBF16CompCumSumSinGen transformation#37274
mangguo321 wants to merge 8 commits into
openvinotoolkit:masterfrom
mangguo321:mang/kokoro_accuracy_fix

Conversation

@mangguo321

Copy link
Copy Markdown
Contributor

Details:

  • Add DisableBF16CompCumSumSinGen transformation to keep l_sin_gen CumSum chain in FP32 when CPU inference precision is BF16 to prevent accumulated phase drift
  • the transformation matching: Transpose -> Interpolate -> Transpose -> CumSum -> Multiply -> Transpose -> Multiply -> Interpolate -> Transpose -> Sin
  • Add unit test

Tickets:

AI Assistance:

  • AI assistance used: no / yes
  • If yes, summarize how AI was used and what human validation was performed (build/tests/manual checks).

@mangguo321
mangguo321 requested review from a team as code owners August 6, 2026 08:47
@mangguo321 mangguo321 changed the title Add DisableBF16CompCumSumSinGen transformation [CPU] Add DisableBF16CompCumSumSinGen transformation Aug 6, 2026
@yuxu42
yuxu42 requested review from zhangYiIntel and a lite review from Copilot August 6, 2026 08:56
@github-actions github-actions Bot added the category: CPU OpenVINO CPU plugin label Aug 6, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Warning

Copilot couldn't run its full agentic review because it didn't start before the timeout. Make sure your repository has a runner available, or add a copilot-code-review.yml file specifying one with the runs-on attribute. See the docs for more details.

Adds a CPU transformation + runtime support to keep the StyleTTS2/iSTFTNet l_sin_gen CumSum→Sin chain in FP32 during BF16 inference to prevent phase drift.

Changes:

  • Introduces DisableBF16CompCumSumSinGen matcher pass that tags matched nodes with “disable f32→bf16 conversion”.
  • Extends CPU Graph BF16 enforcement to preserve a continuous FP32 region around tagged nodes.
  • Adds gtest coverage for positive/negative pattern matching.

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
src/plugins/intel_cpu/tests/unit/transformations/disable_bf16_comp_cumsum_sin_gen_test.cpp Adds unit tests validating the matcher marks expected nodes (and doesn’t match a near-miss).
src/plugins/intel_cpu/src/transformations/transformation_pipeline.cpp Registers the new matcher pass in the CPU transformation pipeline.
src/plugins/intel_cpu/src/transformations/cpu_opset/common/pass/disable_bf16_comp_cumsum_sin_gen.hpp Declares the new matcher pass and documents intended topology/scope.
src/plugins/intel_cpu/src/transformations/cpu_opset/common/pass/disable_bf16_comp_cumsum_sin_gen.cpp Implements the pattern match and tagging via disable_conversion(f32,bf16).
src/plugins/intel_cpu/src/node.h / src/plugins/intel_cpu/src/node.cpp / src/plugins/intel_cpu/src/graph.cpp Plumbs the disable-flag into Node and expands FP32 “skip” region during BF16 precision enforcement.

Comment thread src/plugins/intel_cpu/src/graph.cpp
Comment thread src/plugins/intel_cpu/src/graph.cpp
@maxnick

maxnick commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

@zhangYiIntel , could you please review?
@yuxu42 , FYI, yet another example why we need to switch to ngrpah bf16 markup and align with fp16

@@ -721,6 +724,7 @@ class Node {
MemoryFormatFilter memoryFormatFilter;
bool enforceBF16evenForGraphTail = false;
bool keepOriginalPrecision = false;
bool disableBF16Conversion = false;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

quite strange flag that looks like WA, not the full solution. What if I want to disable conversion from other type

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would not consider this a workaround, but rather an extension of the existing precision-control mechanism in the CPU plugin. We already have a dedicated guard for fp16 (keepOriginalPrecision), so introducing a BF16 specific guard is consistent with current plugin design.

@rkazants rkazants left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would recommend to ask review from @maxnick, @CuriousPanCake, @mryzhov

@rkazants

rkazants commented Aug 6, 2026

Copy link
Copy Markdown
Collaborator

You change common transformation pipeline with which you need to be careful since it can impact many other models. Did you run the full validation on all models?

@@ -2092,6 +2087,16 @@ void Graph::EnforceInferencePrecision() const {

// Pattern-based node skipping for BF16 precision enforcement
if (inferPrec == ov::element::bf16) {
// Preserve a continuous f32 region around model-declared precision-sensitive operations.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For Rope markup in openvino/src/common/transformations/src/transformations/common_optimizations/mark_rope_input_to_keep_in_mixed_precision.cpp, we just use disable_conversion in transformation level why do we need to add a search logic here ?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The disable_conversion is only a marker, while precision enforcement happens later on CPU plugin graph nodes. Without backward/forward closure from marked seeds, the FP32 region may become discontinuous after graph rewrites and optimizers, causing unintended BF16 enforcement inside the intended sensitive chain.

@mangguo321

Copy link
Copy Markdown
Contributor Author

You change common transformation pipeline with which you need to be careful since it can impact many other models. Did you run the full validation on all models?

Hi @rkazants This change is gated by a fixed subgraph match in transformation and also a friendly-name check containing "l_sin_gen" which is mainly TTS/vocoder pipelines, such as the StyleTTS2 / iSTFTNet F0 oscillator path. Unrelated models should not be affected.

auto transpose_pre_m = wrap_type<ov::op::v1::Transpose>({any_input(), any_input()});

auto interp_pre_3_m =
wrap_type<ov::op::v4::Interpolate, ov::op::v11::Interpolate>({transpose_pre_m, any_input(), any_input()});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about v0::Interpolate?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add v0::Interpolate support.

wrap_type<ov::op::v4::Interpolate, ov::op::v11::Interpolate>({transpose_pre_m, any_input(), any_input()});
auto interp_pre_4_m = wrap_type<ov::op::v4::Interpolate, ov::op::v11::Interpolate>(
{transpose_pre_m, any_input(), any_input(), any_input()});
auto interp_pre_m = std::make_shared<ov::pass::pattern::op::Or>(OutputVector{interp_pre_3_m, interp_pre_4_m});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use the shorter '|` syntax for or.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I kept the explicit Or form because switching to | causes compilation failures in this pass.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you sure you're not missing some import? Check how other transformations do it or ask the LLM to do it. I'm pretty sure it should compile.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Switched to shorter '|` syntax for or and compiled pass. Thanks!

wrap_type<ov::op::v4::Interpolate, ov::op::v11::Interpolate>({mul2_m, any_input(), any_input()});
auto interp_down_4_m =
wrap_type<ov::op::v4::Interpolate, ov::op::v11::Interpolate>({mul2_m, any_input(), any_input(), any_input()});
auto interp_down_m = std::make_shared<ov::pass::pattern::op::Or>(OutputVector{interp_down_3_m, interp_down_4_m});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use the shorter '|` syntax for or.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above.

auto interp_down_3_m =
wrap_type<ov::op::v4::Interpolate, ov::op::v11::Interpolate>({mul2_m, any_input(), any_input()});
auto interp_down_4_m =
wrap_type<ov::op::v4::Interpolate, ov::op::v11::Interpolate>({mul2_m, any_input(), any_input(), any_input()});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same question regarding v0::Interpolate

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

if (!node) {
return false;
}
return node->get_friendly_name().find("l_sin_gen") != std::string::npos;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure this is the best way to identify a node. Where is it set?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Btw, you can check it on the pattern level using a predicate without entering the callback.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I originally used it only as an additional safety gate to reduce the matching scope. However, I think the current subgraph pattern is already specific enough. We can rely on the structural match alone and remove the dependency on the node name.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To verify that, additional accuracy validation needs to be run on the changes.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Generally, I agree, the pattern looks complicated enough to avoid false-positives.

Comment on lines +98 to +103
for (const auto& key : {interp_pre_3_m, interp_pre_4_m, interp_down_3_m, interp_down_4_m}) {
auto it = pattern_map.find(key);
if (it != pattern_map.end()) {
to_mark.push_back(it->second.get_node_shared_ptr());
}
}

@CuriousPanCake CuriousPanCake Aug 7, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you recognizing what nodes have been matched by Or? You can extract them by using the 'Or' handle: pattern_map.at(interp_down_m). Btw, if you want to mark all the matched nodes by the pattern, consider just using Matcher::get_matched_nodes() and marking them in a loop. That would help you to get rid off the mark_fp32_chain() function.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done. Thanks!

return false;
}

if (!is_l_sin_gen_node(sin_node)) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same concern here, friendly name could change in different torch/transformer version, if we are using the pattern matching, could you find some other more deterministic criteria ?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above, I think the current subgraph pattern is already specific enough. We can rely on the structural match alone. I'll remove the dependency on the node name.

@mangguo321
mangguo321 force-pushed the mang/kokoro_accuracy_fix branch from c859eaa to 10f432f Compare August 7, 2026 08:53
Comment on lines +32 to +40
auto transpose_pre_m = wrap_type<ov::op::v1::Transpose>({any_input(), any_input()});

auto interp_pre_v0_m = wrap_type<ov::op::v0::Interpolate>({transpose_pre_m, any_input()});
auto interp_pre_v4_m = wrap_type<ov::op::v4::Interpolate>({transpose_pre_m, any_input(), any_input()});
auto interp_pre_v4_with_axes_m =
wrap_type<ov::op::v4::Interpolate>({transpose_pre_m, any_input(), any_input(), any_input()});
auto interp_pre_v11_m = wrap_type<ov::op::v11::Interpolate>({transpose_pre_m, any_input()});
auto interp_pre_v11_with_axes_m =
wrap_type<ov::op::v11::Interpolate>({transpose_pre_m, any_input(), any_input()});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe, it would be useful here to build a helper like 'interploate_variations(input)` that would produce all the alternatives for an Interpolate node. It looks like below you're doing the same thing so there's a double reason for that :)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done, thanks!

@mangguo321
mangguo321 force-pushed the mang/kokoro_accuracy_fix branch from 8458650 to e57c0bf Compare August 10, 2026 08:25
@mangguo321
mangguo321 force-pushed the mang/kokoro_accuracy_fix branch from e57c0bf to a18c78a Compare August 11, 2026 01:46
@mangguo321

Copy link
Copy Markdown
Contributor Author

Hi @CuriousPanCake @zhangYiIntel @rkazants All review comments have been addressed. Could you please take another look? Thanks!

Comment on lines +29 to +41
const std::string l_sin_gen_prefix = "__module.decoder.generator.m_source.l_sin_gen/";

// Names used to look up matched nodes after the pass has run.
const std::string name_transpose_pre = "transpose_pre";
const std::string name_interp_pre = "interp_pre";
const std::string name_transpose_pre_cumsum = "transpose_pre_cumsum";
const std::string name_cumsum = "cumsum";
const std::string name_mul_after_cumsum = "mul_after_cumsum";
const std::string name_transpose_after_mul = "transpose_after_mul";
const std::string name_scale_mul = "scale_mul";
const std::string name_interp_after = "interp_after";
const std::string name_transpose_after_interp = "transpose_after_interp";
const std::string name_sin = l_sin_gen_prefix + "aten::sin/Sin";

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we still need these? I think, we ditched the idea of name-based matching?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These names are only used in test assertions to identify nodes and verify their precision in test case. They are not used for name-based pattern matching in transformation pass. I also simplified the Sin node name to "sin" and removed the unused "l_sin_gen" prefix.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants