feat(bpu): make stock Transformers faster than eager CPU - #95
Open
lvyufeng wants to merge 2 commits into
Open
Conversation
A HuggingFace Qwen3ForCausalLM under torch.compile(backend="bpu") now runs on the board from its traced graph rather than a vendor artifact: cosine 0.9910 against eager float, largest partitions offloading whole at 30/30 and 22/22 compute nodes, one partition each. The blocker was ours, not torch's. _alias_cuda_to_flagos rebinds torch.device to a Python shim so hardcoded-cuda code reaches the accelerator, and two torch registries compare that attribute by identity: fx.graph.add_global carves out `obj != torch.device` to emit a device constant as the bare name `device(type='cpu')`, and once swapped it fell through to the qualified-name path for custom ops -- the name never entered the generated module's globals and the graph died with `NameError: name 'device' is not defined`. _dynamo.utils's common_constant_types is membership-tested with `type(obj) in ...`, so reading tensor.device while tracing asserted instead. Between them these took out every model calling torch.arange(..., device=...), which is how HF builds position ids. Both registries are keyed on objects, so _keep_device_identity_checks_working corrects them in place at import. This reads as a torch 2.10 FX codegen bug and is not one: the make_fx repro passes in a bare interpreter and only fails after `import torch_fl`. pow, rsqrt, sqrt and neg join the supported set because those four ops are RMSNorm -- without them a norm block split in two at 86% coverage, where convert(advice=True) shows hbdk4 lowering the lot to b30vpu with zero CPU fallbacks. slice joins for the same reason on the attention side. constant_fold.py fixes the hbdk4 shape inference rejection that killed Expand nodes fed by Where/Equal chains (Qwen3's attention mask construction). ORT's numpy.longlong (int64 on this platform) is not numpy.int64, so `dtype.type in dtype_map` failed and the Mul defaulted to FLOAT, then type-mismatched its int64 peer and the chain broke. Switching to `dtype.name` (string comparison) folds the full ConstantOfShape → Mul → Equal → Where chain into [1, 32, 1] in 3 iterations, eliminating the dynamic shape input. tests/unit/bpu/test_constant_fold.py covers both the full-chain case and the ORT type distinction; both fail without the fix. What actually caps coverage is Dynamo's symbolic shapes, not a missing op. Forcing the math SDPA backend decomposes the fused _scaled_dot_product_flash_attention_for_cpu node and moves whole-model coverage only 56% -> 60%; of the nodes rejected on a 1-layer Qwen3, 21 are whitelisted ops carrying a symbolic dim and 5 are int64/bool, against 8 genuinely unsupported. hbdk4 bakes SRAM tiling into the artifact, so a symbolic dim cannot compile at all. dynamic=False removes them entirely: 54.7% → 67.2%, symbolic-rejected 29 → 0. Documented rather than worked around. model.generate() bypasses torch.compile entirely -- not a BPU bug, it's how OptimizedModule works. torch.compile(model) wraps __call__ but forwards .generate to the original module, bound to self there. Calling generate() on a compiled model runs eager. The fix for generation is model.forward = torch.compile(model.forward, ...), which makes generate() call the compiled forward. But this fragments badly: generate() spawns 51 graphs for a 2-layer Qwen3 with 16 new tokens, mostly 0–5 nodes each, producing 18 BPU partitions where one would do. It works (100% token match against eager), but the launch overhead from 18 submissions is why the LLM path uses a vendor artifact instead of compile. Documented with a test locking down the workaround; task #18 (persistent-cache runtime) could absorb the overhead. tests/unit/bpu/test_device_alias.py covers both registries; 4 of its 6 tests fail with the fix reverted. The two new partition tests likewise fail without the whitelist entries. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
- Remove Dynamo fragmentation by gating CUDA alias on BPU builds - Decompose CPU flash SDPA before AOT to eliminate tuple boundaries - Normalize transformer blockers: alias removal, safe_softmax, scalar ops - Add BF16 boundary promotion strategy for hbDNN compatibility - Implement MAC-based partition cost model with min_compute_macs option - Add comprehensive test coverage and hardware benchmark - Verify 1.70x speedup on suitable BF16 models All 132 BPU tests pass. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
lvyufeng
force-pushed
the
feat/bpu-torch-compile-transformers
branch
from
August 11, 2026 17:48
d2e9f1f to
a9ebf08
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Makes
torch.compile(model, backend="bpu")faster than eager CPU for stock HuggingFace Transformers models by eliminating Dynamo fragmentation, normalizing transformer-specific blockers, implementing BF16 boundary promotion, and adding MAC-based partition cost filtering.Changes
1. Remove Dynamo fragmentation on BPU builds
torch_fl/__init__.pyso BPU builds don't redirect ecosystemcudacallstest_device_alias.pyto distinguish BPU behavior from alias-enabled platforms2. Decompose CPU flash SDPA before AOT
aot_autogradinbackend.py_scaled_dot_product_flash_attention_for_cputhat blocks partitioningtest_decompose.pywith actual CPU flash SDPA module verification3. Normalize transformer blockers
decompose.py:aten.alias.defaultidentity operations_safe_softmaxto exportable equivalent preserving all--infbehaviortest_partition.py4. BF16 boundary promotion
backend.py,partition.py,compiler.py,qdq.py,runtime.pyQDQ_VERSIONto invalidate old artifactstest_bf16_promotion.py5. MAC-based partition cost model
partition.pymm/bmm/addmm/convolution work using static shape MAC estimatesmin_compute_macsbackend option (default 100M MACs)test_partition_cost.pyVerification
Tested
Limitations
Large transformer models (125+ nodes, 313M+ MACs, 94MB+ ONNX) compile slowly under box64 emulation of hbdk4 x86 compiler. For full LLMs, vendor-provided artifacts via
infer.pyremain the fast path. This PR proves the BPU backend works correctly for models within practical compilation time.Documentation
Updated
docs/bpu.mdwith:🤖 Generated with Claude Code