Skip to content

[BugFix][SFA] Return zero softmax sums for empty A2/A3 DCP rows - #17823

Draft
LiPu-jpg wants to merge 1 commit into
vllm-project:mainfrom
LiPu-jpg:codex/sfa-output-statistics-20260930
Draft

LiPu-jpg wants to merge 1 commit into
vllm-project:mainfrom
LiPu-jpg:codex/sfa-output-statistics-20260930

Conversation

@LiPu-jpg

@LiPu-jpg LiPu-jpg commented Sep 30, 2026 •

Copy link
Copy Markdown
Contributor

What this PR does / why we need it?

An A2/A3 unquantized SFA row with a nonempty local KV cache but only -1 sparse indices returns a zero attention output while reporting a nonzero softmax sum (512 in the regression). Consequently, softmax_max + log(softmax_sum) is a finite sentinel instead of the empty row's -inf LSE.

AscendSFADCPImpl compacts valid local indices before padding and uses these statistics to merge local attention results. Detect an empty prefix in its TND/PA_BSND, token-wise, sparse_mode=0, return-LSE path and reuse the existing zero-output path. Valid rows retain the existing computation; pipeline draining is preserved. Add independent FP64-reference regressions for FP16/BF16, RoPE/NoPE, permuted physical pages, variable local KV lengths, multiple KV tiles, and graph replay from nonempty to empty and back. Correct the operator documentation's inference-LSE and NoPE restrictions.

This complements merged #16656 (A5). It does not trim nonempty prefixes as proposed by #17653; that draft explicitly preserves the old empty-prefix path. No competition source was copied.

Does this PR introduce any user-facing change?

Empty local sparse rows in the scoped A2/A3 return-LSE path now return zero attention output and zero softmax sum, giving -inf when reconstructing LSE. The public signature is unchanged.

How was this patch tested?

Draft limitations: A2 hardware correctness and paired operator performance are verified. A3 compilation/hardware validation and model-level validation remain pending. No model throughput improvement is claimed.

  • Full bash format.sh ci: passed (including the new test file). GitHub pre-commit, DCO, and CPU UT on both the pinned vLLM main revision and v0.30.0 also passed. The CI gate is currently red because a maintainer must add a precision/full-test label; selected NPU tests have not run.
  • Ascend 910B3, CANN 9.1.0: fresh isolated SFA kernel build passed with bash build.sh --opkernel --soc=ascend910b --ops=sparse_flash_attention -j2 from csrc.
  • Native upstream baseline: 20 passed, 16 failed; failures identify nonzero empty-row softmax sums / finite reconstructed LSE.
  • Candidate: 36 passed in 13.42 seconds, including empty final rows after multiple KV tiles and dynamic graph replay. The same FP64 logical-token reference and tolerances are used for both.
  • Baseline SFA host/kernel sources (19 files) match upstream e86df70; baseline and candidate use identical Python, host adapter and runtime libraries in separate private package copies. Only the generated SFA .o and .json files are replaced for the candidate. Both installed candidate object hashes match the fresh build and differ from baseline.
  • Paired operator performance: 36 configurations, 3 independent processes per variant, 5 samples per process/configuration (15 per variant/configuration), order B/C/C/B/B/C. All six complete runs passed the interference guards. Empty-row median latency decreased 55.99–61.76%; 16/36 configurations had higher candidate medians, with a maximum observed increase of 2.36%. Some sample ranges overlap; these small differences are observations, not significance claims. Peak allocated NPU memory was 121,112,064 bytes in every run. Full cases and raw samples are below.
  • Two earlier attempts were interrupted by conservative detection of other processes loading torch_npu; their full logs and partial measurements are retained separately and not used in the paired comparison. These detections do not prove concurrent NPU kernel execution. No cases were removed from any completed run.

Reproduce in a supported CANN/torch_npu/vLLM environment with the custom operators built from this branch:

SOC_VERSION=ascend910b3 COMPILE_CUSTOM_KERNELS=1 python setup.py build_ext --inplace
python -m pytest -q \
  tests/e2e/nightly/single_node/ops/singlecard_ops/test_sparse_flash_attention_lse_a2_a3.py
bash format.sh ci

To reproduce the baseline, run the same added test file with a custom-op installation built from e86df70. Keep the installations in separate environments so a cached native library or vendor OPP package cannot hide which kernel is loaded.

Verified generated/installed candidate kernel SHA256:

2b1d76e5c4e23bbe39862e19198487285650c84e88b2245a9877dc3002d6177c  SparseFlashAttention_04d19c29334449b1853b913ad67aa9d4.o
469eec4891d826aa41d4a3bbc12ddc2b68c33c53e450d611eedc44addb45a06f  SparseFlashAttention_2e25ad8fefac1f04c03bd78f2d2ab366.o

Operator-only performance evidence

Ascend 910B3 (physical device 3), CANN 9.1.0, torch 2.10.0+cpu with torch_npu 2.10.0.post4. Baseline and candidate both use Release/O3 kernel compilation. Identical framework/host libraries and deterministic inputs; separate vendor package copies. Test file SHA256: 89bd64c40d1e53a3b50d09998122e77212b70b608a663da5f16bd45fa6bdb206. Runs completed consecutively on 2026-09-30, approximately 13:55–13:59 Asia/Shanghai.

Each graph contains 8 SFA invocations; warm up 5 eager calls and 10 graph replays. Each sample times 100 replays with NPU events and divides by 800 invocations. Keep 5 samples per process, repeat each variant in 3 processes. Checks before/after every configuration reject other torch_npu/compilation processes. This measures amortized device graph execution for a repeatedly reused input/cache; it excludes setup/reference work and is not serving or model throughput. Query heads=64, D=512, page size=128, sparse capacity=2048, mode=0, return-LSE=True. Single-row cases use KV=512; varlen uses Q lengths [1,3,2], KV [0,129,512]; pipeline uses Q [7,28], KV [4096,4096], counts [0,1,513,0,1025,2048,0] repeated five times; graph-replay-input uses Q=2 and counts [1,129].

Positive latency change means slowdown.

All 36 configurations: min/median/max and latency changes
dtype RoPE case baseline min/median/max (us) candidate min/median/max (us) latency change
torch.float16 0 selected-0 32.504/32.717/34.136 13.734/14.367/14.450 -56.09%
torch.float16 0 selected-1 32.386/32.706/32.885 32.263/32.762/33.249 +0.17%
torch.float16 0 selected-127 35.060/35.504/35.643 34.779/35.815/36.078 +0.88%
torch.float16 0 selected-128 35.009/35.444/35.755 34.672/35.587/35.954 +0.40%
torch.float16 0 selected-129 35.226/35.338/35.708 34.523/35.625/36.305 +0.81%
torch.float16 0 selected-257 39.879/40.117/40.161 39.661/40.445/40.774 +0.82%
torch.float16 0 varlen 43.208/43.287/45.056 42.834/43.261/44.499 -0.06%
torch.float16 0 pipeline 137.782/138.593/140.858 133.913/136.169/136.596 -1.75%
torch.float16 0 graph-replay-input 37.064/37.321/37.445 36.512/36.696/37.376 -1.68%
torch.float16 64 selected-0 37.158/37.303/37.760 13.766/14.435/14.469 -61.30%
torch.float16 64 selected-1 37.448/37.526/37.851 36.896/37.862/38.098 +0.89%
torch.float16 64 selected-127 39.057/39.161/40.233 38.845/39.772/41.081 +1.56%
torch.float16 64 selected-128 39.379/39.715/39.979 38.828/39.587/40.347 -0.32%
torch.float16 64 selected-129 39.031/39.845/40.268 38.794/39.528/40.225 -0.79%
torch.float16 64 selected-257 43.648/44.275/44.705 42.894/43.717/44.528 -1.26%
torch.float16 64 varlen 47.078/47.179/47.480 46.343/46.720/47.079 -0.97%
torch.float16 64 pipeline 160.030/161.307/161.806 151.889/152.692/155.759 -5.34%
torch.float16 64 graph-replay-input 40.685/41.092/41.149 39.890/40.818/41.210 -0.67%
torch.bfloat16 0 selected-0 32.613/32.704/33.949 13.739/14.394/15.211 -55.99%
torch.bfloat16 0 selected-1 32.797/32.883/33.114 32.179/33.183/33.818 +0.91%
torch.bfloat16 0 selected-127 35.190/35.554/35.774 34.758/36.112/36.649 +1.57%
torch.bfloat16 0 selected-128 35.000/35.299/36.881 35.064/36.131/36.433 +2.36%
torch.bfloat16 0 selected-129 35.021/35.546/36.054 34.982/36.092/36.578 +1.54%
torch.bfloat16 0 selected-257 39.569/40.451/41.253 39.971/41.095/41.291 +1.59%
torch.bfloat16 0 varlen 43.086/43.512/43.756 42.725/43.338/43.899 -0.40%
torch.bfloat16 0 pipeline 138.557/138.727/139.377 135.020/136.105/136.495 -1.89%
torch.bfloat16 0 graph-replay-input 37.198/37.332/38.222 36.118/36.633/38.122 -1.87%
torch.bfloat16 64 selected-0 37.635/37.766/38.389 13.756/14.442/15.317 -61.76%
torch.bfloat16 64 selected-1 37.584/37.818/37.868 37.032/38.229/38.623 +1.09%
torch.bfloat16 64 selected-127 39.645/40.003/40.081 38.932/40.353/40.405 +0.88%
torch.bfloat16 64 selected-128 39.698/39.918/40.656 38.764/40.459/41.706 +1.36%
torch.bfloat16 64 selected-129 39.673/39.731/40.423 38.869/40.287/40.797 +1.40%
torch.bfloat16 64 selected-257 44.019/44.620/46.047 43.442/44.377/45.106 -0.55%
torch.bfloat16 64 varlen 47.317/47.890/48.014 46.448/46.980/47.290 -1.90%
torch.bfloat16 64 pipeline 160.639/160.978/161.430 152.694/153.590/154.170 -4.59%
torch.bfloat16 64 graph-replay-input 41.190/41.235/43.153 40.301/41.046/42.042 -0.46%
All 1,080 measured samples and paired process ratios
dtype,rope_dim,case,baseline_samples_us,candidate_samples_us,paired_ratio_min,paired_ratio_median,paired_ratio_max
torch.float16,0,selected-0,"32.711749;32.717276;32.700424;32.690351;32.717025;32.533901;32.543650;32.504425;32.508974;34.135599;32.851150;32.819176;32.801750;32.832050;33.984351","14.398775;14.368550;14.355600;14.366975;14.366025;14.450074;14.415200;14.419775;14.416500;14.446599;13.776525;13.766625;13.738075;13.734200;13.734900",0.418435,0.439199,0.443223
torch.float16,0,selected-1,"32.714751;32.715974;32.699699;32.705951;32.704301;32.396150;32.386174;32.393200;32.395725;32.393675;32.875950;32.849901;32.861900;32.884824;32.865400","32.798076;32.773726;32.819924;32.792425;32.783151;32.761650;32.746975;32.731249;32.763700;32.749650;33.248575;32.262599;32.286899;32.282400;32.336726",0.982398,1.002644,1.010989
torch.float16,0,selected-127,"35.508699;35.500975;35.479374;35.503750;35.518425;35.060351;35.085726;35.066400;35.086901;35.131774;35.594549;35.630324;35.634999;35.637650;35.642874","35.846825;35.814774;35.813725;35.800700;35.815225;36.046150;36.070275;36.059351;36.078274;36.071975;34.815376;34.778974;34.811125;34.812076;34.804325",0.976880,1.008760,1.028061
torch.float16,0,selected-128,"35.452249;35.444200;35.468276;35.405674;35.429926;35.008974;35.021000;35.041499;35.040200;35.021074;35.755024;35.752726;35.731025;35.715725;35.755074","35.587201;35.596676;35.562251;35.643051;35.577526;35.932574;35.953774;35.927825;35.906999;35.903449;34.701049;34.672050;34.687901;34.675775;34.725699",0.970217,1.004035,1.025892
torch.float16,0,selected-129,"35.335975;35.356901;35.337675;35.321450;35.364699;35.225625;35.288401;35.273349;35.274351;35.279324;35.708151;35.688825;35.689776;35.650926;35.692799","35.627000;35.625074;35.634975;35.589526;35.592926;36.304874;36.282876;36.285775;36.281176;36.266899;34.546225;34.523175;34.523926;34.578850;34.535275",0.967652,1.008133,1.028591
torch.float16,0,selected-257,"39.901800;39.913974;39.912701;39.879200;39.915900;40.106626;40.123525;40.160751;40.144548;40.141902;40.116849;40.131373;40.148449;40.113125;40.138302","40.438676;40.438027;40.447574;40.445375;40.459599;40.774250;40.731401;40.726776;40.731101;40.717549;39.681375;39.669149;39.670050;39.661076;39.683375",0.988505,1.013346,1.014678
torch.float16,0,varlen,"43.275423;43.295350;43.286724;43.289199;43.283424;43.219476;43.214827;43.247852;43.225474;43.207927;43.879824;43.893299;45.055900;43.901024;43.885250","44.498549;43.683276;43.700900;43.691301;43.669176;43.211751;43.225999;43.265424;43.260550;43.268800;42.833848;42.862601;42.834501;42.838426;42.838850",0.975967,1.000950,1.009346
torch.float16,0,pipeline,"137.819405;137.818575;137.793703;137.781630;137.832623;140.799227;140.811548;140.858078;140.820627;140.821953;138.604097;138.605423;138.589754;138.589029;138.592825","136.181803;136.142998;136.169176;136.178875;136.162996;136.551771;136.566048;136.596298;136.551476;136.542149;133.913097;133.915577;134.235249;133.917103;133.929548",0.966263,0.969686,0.988032
torch.float16,0,graph-replay-input,"37.444625;37.412851;37.424674;37.421200;37.418251;37.236526;37.300899;37.325101;37.321100;37.333524;37.129126;37.073774;37.076199;37.064476;37.102976","36.680825;36.663475;36.708574;36.706901;36.695800;37.337325;37.375925;37.341175;37.367401;37.359099;36.534824;36.521649;36.515200;36.512151;36.545825",0.980615,0.985043,1.001018
torch.float16,64,selected-0,"37.174201;37.211850;37.177725;37.178550;37.157750;37.306700;37.302749;37.302899;37.284725;37.297649;37.759025;37.732501;37.750375;37.759576;37.750325","14.435101;14.439050;14.462950;14.426925;14.411900;14.465525;14.468900;14.463400;14.465675;14.467450;13.792075;13.767675;13.765875;13.772250;13.797050",0.364824,0.387791,0.388273
torch.float16,64,selected-1,"37.447975;37.484274;37.478676;37.479501;37.457175;37.522950;37.555101;37.526076;37.543449;37.511425;37.851124;37.822475;37.814176;37.822726;37.814524","37.888200;37.844174;37.861924;37.861700;37.863824;38.041174;38.097675;38.039100;38.085799;38.089700;36.896451;36.899924;36.914775;36.916625;36.910474",0.975887,1.010226,1.014916
torch.float16,64,selected-127,"39.097049;39.070225;39.068699;39.079125;39.057274;39.160550;39.159174;39.160049;39.171901;39.204276;40.212626;40.233450;40.228925;40.226250;40.188627","39.771774;39.756451;39.742401;39.779301;41.080952;40.407801;40.404549;40.387650;40.402398;40.394502;38.845150;38.868849;38.873949;38.892274;38.885224",0.966383,1.017956,1.031712
torch.float16,64,selected-128,"39.389875;39.404476;39.378650;39.414325;39.404075;39.733925;39.715326;39.651625;39.979401;39.688849;39.954474;39.947000;39.917901;39.958351;39.895525","39.604950;39.586775;39.597001;39.561174;39.580300;40.347223;40.335751;40.344625;40.323400;40.336699;38.901050;38.884525;38.847749;38.827825;38.865349",0.972923,1.004637,1.015646
torch.float16,64,selected-129,"39.067700;39.069850;39.035475;39.074199;39.031100;39.877574;39.844675;39.831126;39.841876;39.855375;40.240173;40.239549;40.238299;40.268102;40.207477","39.507926;39.528124;39.523475;39.530900;39.540451;40.210676;40.224924;40.219274;40.219202;40.178299;38.795226;38.793974;38.797951;38.799825;38.813601",0.964175,1.009400,1.011785
torch.float16,64,selected-257,"44.704952;43.659902;43.668399;43.648100;43.669100;44.279823;44.277525;44.245200;44.261527;44.253950;44.314599;44.275250;44.283276;44.337802;44.322224","43.770351;43.723650;43.717251;43.661299;43.673849;44.478350;44.519973;44.512601;44.527826;44.456625;42.954001;42.899275;42.898273;42.906723;42.893701",0.968062,1.001119,1.005673
torch.float16,64,varlen,"47.182651;47.168751;47.178898;47.184925;47.170224;47.112098;47.099800;47.077851;47.122650;47.091851;47.479925;47.459526;47.470851;47.460923;47.448974","47.041326;47.022123;47.055626;47.078676;47.037773;46.720300;46.677999;46.715598;46.738024;46.748824;46.365752;46.376424;46.374650;46.342926;46.366048",0.976931,0.991943,0.997084
torch.float16,64,pipeline,"161.806068;161.753616;161.764679;161.714096;161.770897;160.091000;160.037498;160.059700;160.048351;160.030327;161.296654;161.306553;161.298504;161.356621;161.320896","155.728846;155.719976;155.715075;155.759001;155.736151;151.962671;151.925097;151.944199;151.888771;151.946497;152.671051;152.675505;152.691650;152.734623;152.692900",0.946593,0.949364,0.962688
torch.float16,64,graph-replay-input,"40.710225;40.729027;40.759501;40.710249;40.684948;41.148949;41.106524;41.106176;41.074100;41.067677;41.133275;41.092048;41.099176;41.143250;41.117873","40.837898;40.744376;40.818200;40.794473;40.818524;41.210499;41.180377;41.164598;41.148376;41.190577;39.966700;39.899375;39.890125;39.925449;39.895699",0.970366,1.001805,1.002652
torch.bfloat16,0,selected-0,"32.641850;33.788099;32.623999;32.633300;32.613449;32.704451;32.677100;32.696075;32.719550;32.692125;33.085024;33.075025;33.099875;33.088150;33.948925","15.204700;15.210925;15.198600;15.196800;15.196725;14.433100;14.379725;14.393950;14.393975;14.411200;13.740950;13.738750;13.776475;13.775300;13.773251",0.416259,0.440236,0.465739
torch.bfloat16,0,selected-1,"32.898076;32.906475;32.874849;32.882750;32.858200;32.796974;32.814975;32.811301;32.807901;32.809224;33.112326;33.102875;33.113999;33.110399;33.105824","33.797951;33.768799;33.818326;33.777676;33.753974;33.231175;33.164849;33.183124;33.172326;33.195624;32.181125;32.231176;32.178700;32.181749;32.180674",0.971934,1.011396,1.027216
torch.bfloat16,0,selected-127,"35.773575;35.763924;35.760026;35.770049;35.731976;35.209775;35.207026;35.189724;35.214076;35.222025;35.558975;35.553925;35.562425;35.521574;35.532074","36.631250;36.644976;36.637375;36.648674;36.606600;36.141751;36.157725;36.101425;36.087074;36.111825;34.824824;34.811349;34.757750;34.764099;34.764225",0.977789,1.024423,1.025619
torch.bfloat16,0,selected-128,"36.881425;36.847551;36.867976;36.872101;36.868274;35.000124;35.050750;35.055525;35.048726;35.016451;35.296400;35.304425;35.287275;35.303824;35.298674","36.432674;36.401751;36.412625;36.426075;36.404276;36.118250;36.147075;36.134851;36.124525;36.131201;35.096550;35.063901;35.972800;35.089276;35.081124",0.987641,0.994068,1.030885
torch.bfloat16,0,selected-129,"36.054325;36.039600;35.980175;35.990751;35.992725;35.063875;35.065899;35.050924;35.020599;35.049825;35.569825;35.545826;35.551624;35.500126;35.535176","36.568174;36.577525;36.545000;36.550400;36.569574;36.104250;36.084876;36.092451;36.086476;36.123500;34.981849;35.037625;35.012250;34.994099;35.042601",0.984989,1.015988,1.029715
torch.bfloat16,0,selected-257,"41.247573;41.232977;41.220527;41.252575;41.201124;39.593101;39.595475;39.621074;39.625876;39.568601;40.452027;40.445423;40.454001;40.451450;40.400276","41.257524;41.291175;41.221375;41.219249;41.235175;41.103301;41.069074;41.078901;41.115975;41.095176;40.037251;39.983976;39.978700;39.970851;39.983225",0.988425,1.000053,1.037876
torch.bfloat16,0,varlen,"43.085976;43.096223;43.110652;43.094749;43.113275;43.512273;43.496351;43.517623;43.562527;43.497324;43.701348;43.752475;43.735800;43.735452;43.756051","43.899250;43.895650;43.854227;43.863549;43.883777;43.338227;43.338127;43.328948;43.359275;43.344951;42.727175;42.754474;42.724824;42.766275;42.731924",0.977047,0.996000,1.018274
torch.bfloat16,0,pipeline,"138.727150;138.703699;138.720350;138.742723;138.741055;138.584471;138.556576;138.601246;138.670979;138.582897;139.376574;139.324427;139.301329;139.324102;139.312248","136.064749;136.154871;136.128149;136.105299;136.081696;136.494703;136.434746;136.442327;136.437521;136.480703;135.035925;135.019703;135.030346;135.072727;135.058174",0.969222,0.981101,0.984543
torch.bfloat16,0,graph-replay-input,"37.366099;37.282724;37.337174;37.294075;37.332175;37.231851;37.281301;37.227099;37.197800;37.240374;38.222375;38.220551;38.207800;38.210101;38.219924","38.122101;38.093674;38.088250;38.103449;38.091874;36.632774;36.620276;36.639500;36.633976;36.631875;36.193700;36.158400;36.157000;36.118226;36.156299",0.946025,0.983910,1.020398
torch.bfloat16,64,selected-0,"37.682800;37.719250;37.661574;37.635274;37.667325;37.768350;37.769251;37.765951;37.744374;37.759850;38.389375;38.385100;38.377349;38.384449;38.332174","15.294200;15.292300;15.295250;15.314050;15.316650;14.431900;14.439625;14.453675;14.446850;14.441600;13.791475;13.755850;13.758525;13.761125;13.789949",0.358508,0.382397,0.406062
torch.bfloat16,64,selected-1,"37.817526;37.815726;37.839251;37.822676;37.820101;37.589025;37.600999;37.584200;37.604301;37.589824;37.810099;37.853174;37.833574;37.851374;37.868476","38.610849;38.575699;38.599801;38.623450;38.598199;38.233099;38.242900;38.228774;38.219626;38.203926;37.031574;37.040124;37.050550;37.047949;37.059474",0.978774,1.016998,1.020616
torch.bfloat16,64,selected-127,"40.081177;40.032101;40.001402;40.003324;40.002875;39.710300;39.670849;39.645226;39.677300;39.659176;40.014625;39.988775;40.024323;40.034652;40.009823","40.379276;40.384002;40.353198;40.405226;40.384002;40.344276;40.364151;40.385852;40.386176;40.339098;38.968124;38.945100;38.931849;38.957126;38.993549",0.973572,1.009516,1.017476
torch.bfloat16,64,selected-128,"40.595798;40.656252;40.616102;40.639477;40.593824;39.718101;39.706924;39.700975;39.699874;39.698274;39.911749;39.943249;39.918101;39.896276;39.935925","40.634627;40.642624;40.623775;41.706176;40.614376;40.473952;40.475001;40.440125;40.459299;40.431099;38.808551;38.763800;38.796101;38.765950;38.806026",0.971892,1.000456,1.019101
torch.bfloat16,64,selected-129,"39.731250;39.734750;39.733875;39.727676;39.729300;39.685824;39.673350;39.702125;39.682176;39.703975;40.422649;40.337825;40.355949;40.341425;40.389500","40.762477;40.752673;40.797377;40.734873;40.776024;40.262823;40.265651;40.287600;40.287323;40.305524;38.908451;38.869376;38.886726;38.884525;38.912876",0.963593,1.015157,1.025955
torch.bfloat16,64,selected-257,"46.046724;45.271726;45.275002;45.283551;45.275650;44.041948;44.059525;44.018550;44.062076;44.706802;44.619975;45.688748;44.619699;44.607100;44.593000","45.100050;45.077848;45.035100;45.046377;45.106249;44.379277;44.376574;44.369850;44.358950;44.404802;43.441577;43.447027;43.443475;43.448877;43.465152",0.973719,0.995631,1.007196
torch.bfloat16,64,varlen,"47.317052;47.333026;47.375951;47.359724;47.351475;47.907600;47.920852;47.888551;47.865825;47.889624;47.992573;48.014274;47.967501;48.004451;48.006773","47.273998;47.289848;47.254601;47.263274;47.250700;47.012000;46.974025;46.979852;46.977577;46.982627;46.488276;46.478324;46.465473;46.447649;46.467624",0.967986,0.981003,0.998137
torch.bfloat16,64,pipeline,"160.682774;160.638752;160.709782;160.640774;160.674858;161.417274;161.429749;161.411324;161.411171;161.428432;160.978107;160.964375;160.961304;160.985794;161.006718","153.590422;153.597021;153.568201;153.584623;153.601046;154.169321;154.134998;154.136496;154.117823;154.170399;152.694149;152.709503;152.729025;152.749529;152.732172",0.948756,0.954895,0.955908
torch.bfloat16,64,graph-replay-input,"41.190600;41.190300;41.226726;41.190825;42.467799;41.228075;41.192198;41.246076;41.234875;41.229048;42.021651;41.980600;41.958923;43.153300;41.973848","41.888752;41.867676;41.858850;41.879849;41.912098;41.070251;41.035376;41.042352;42.042198;41.046000;40.306277;40.313549;40.348902;40.301123;40.377827",0.960290,0.995560,1.016728
Reproducible benchmark script

Save this script as benchmark_sfa.py in a fresh benchmark directory. Copy the regression file into tests/test_sparse_flash_attention_lse_a2_a3.py beneath that directory. In each isolated baseline/candidate environment, with its matching custom-op package on PYTHONPATH, run python benchmark_sfa.py OUTPUT.json LABEL. Use the B/C/C/B/B/C process order above; ensure the same NPU is free of other work.

"""Operator-only paired benchmark; keep every trial, including slowdowns."""

import importlib.util
import hashlib
import json
import os
import sys
import time
from pathlib import Path

import torch
import torch_npu  # noqa: F401
import vllm_ascend

test_path = Path(__file__).parent / "tests/test_sparse_flash_attention_lse_a2_a3.py"
spec = importlib.util.spec_from_file_location("sfa_regression", test_path)
test = importlib.util.module_from_spec(spec)
spec.loader.exec_module(test)
assert test.enable_custom_op()


def check_other_npu_processes():
    busy = []
    for p in Path('/proc').glob('[0-9]*/maps'):
        if int(p.parent.name) == os.getpid():
            continue
        try:
            if 'libtorch_npu' in p.read_text():
                busy.append(p.parent.name)
            args = [x.decode(errors='replace') for x in (p.parent / 'cmdline').read_bytes().split(b'\0') if x]
            if args and (Path(args[0]).name in ('cmake', 'bisheng', 'cc1plus') or 'opc_tool/opc.py' in ' '.join(args[1:])):
                busy.append(p.parent.name)
        except (OSError, UnicodeError):
            pass
    if busy:
        print(f'INVALID_WINDOW: other torch_npu or compiler processes detected: {sorted(set(busy))}', flush=True)
        raise SystemExit(75)


def configurations():
    for dtype in (torch.float16, torch.bfloat16):
        for rope in (0, 64):
            for count in (0, 1, 127, 128, 129, 257):
                yield dtype, rope, f"selected-{count}", [1], [512], [count], 512
            yield dtype, rope, "varlen", [1, 3, 2], [0, 129, 512], [0, 0, 1, 129, 128, 257], 512
            yield dtype, rope, "pipeline", [7, 28], [4096, 4096], [0, 1, 513, 0, 1025, 2048, 0] * 5, 4096
            yield dtype, rope, "graph-replay-input", [2], [512], [1, 129], 512


@torch.inference_mode()
def main():
    rows = []
    for dtype, rope, kind, qlens, kvlens, counts, capacity in configurations():
        check_other_npu_processes()
        inputs, _, _ = test._make_inputs(dtype, rope, qlens, kvlens, counts, kv_capacity=capacity)
        for _ in range(5):
            test._run(inputs)
        torch.npu.synchronize()
        graph = torch.npu.NPUGraph()
        # Eight invocations per replay amortize host graph launch overhead.
        with torch.npu.graph(graph):
            results = [test._run(inputs) for _ in range(8)]
        for _ in range(10):
            graph.replay()
        torch.npu.synchronize()
        measurements = []
        for _ in range(5):
            start = torch.npu.Event(enable_timing=True)
            end = torch.npu.Event(enable_timing=True)
            start.record()
            for _ in range(100):
                graph.replay()
            end.record()
            end.synchronize()
            measurements.append(start.elapsed_time(end) * 1000 / 800)
        row = dict(dtype=str(dtype), rope_dim=rope, case=kind, query_lengths=qlens,
                   kv_lengths=kvlens, selected_counts=counts, samples_us=measurements)
        rows.append(row)
        print(json.dumps(row), flush=True)
        check_other_npu_processes()
        del results, graph, inputs
    Path(sys.argv[1]).write_text(json.dumps(dict(label=sys.argv[2], timestamp=time.time(),
        pid=os.getpid(), device=torch.npu.get_device_name(0), torch_version=torch.__version__,
        torch_npu_version=torch_npu.__version__, package_path=str(Path(vllm_ascend.__file__).parent),
        custom_opp_path=os.environ.get('ASCEND_CUSTOM_OPP_PATH'),
        test_sha256=hashlib.sha256(test_path.read_bytes()).hexdigest(),
        max_memory_allocated_bytes=torch.npu.max_memory_allocated(), rows=rows), indent=2) + "\n")


main()

Signed-off-by: Jiao Ziang <2720649216@qq.com>
@gemini-code-assist

Copy link
Copy Markdown
Contributor

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request addresses a bug in the Sparse Flash Attention (SFA) implementation for Ascend A2/A3 hardware where empty sparse rows incorrectly reported non-zero softmax sums. By forcing these rows to return zero attention output and zero softmax sum, the system now correctly produces a negative infinity LSE, maintaining consistency in DCP decode operations. The changes include kernel-level logic updates and a new end-to-end regression test suite to ensure robustness across various inference configurations.

Highlights

  • Bug Fix: Corrected the Sparse Flash Attention (SFA) kernel for A2/A3 hardware to return zero attention output and zero softmax sum for empty rows, ensuring correct LSE reconstruction.
  • Kernel Logic: Updated SparseFlashAttentionMla to detect empty sparse rows in the TND/PA_BSND path and explicitly trigger the zero-output path.
  • Regression Testing: Added a comprehensive test suite covering FP16/BF16, RoPE/NoPE configurations, and graph replay scenarios to validate the fix.
  • Documentation: Updated operator documentation to clarify NoPE support and behavior for empty sparse rows.
New Features

🧠 You can now enable Memory (public preview) to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize the Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counterproductive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution. ↩

@github-actions github-actions Bot added documentation Improvements or additions to documentation module:tests labels Sep 30, 2026
@github-actions

Copy link
Copy Markdown
Contributor

👋 Hi! Thank you for contributing to the vLLM Ascend project. The following points will speed up your PR merge:‌‌

  • A PR should do only one thing, smaller PRs enable faster reviews.
  • Every PR should include unit tests and end-to-end tests ‌to ensure it works and is not broken by other future PRs.
  • Write the commit message by fulfilling the PR description to help reviewer and future developers understand.

If CI fails, you can run linting and testing checks locally according Contributing and Testing.


Tip

💡 Consider Linking a Related Issue or RFC

Your PR title contains the [BugFix] tag, indicating a bug fix or new feature.

Linking a related issue or RFC in the PR description is strongly encouraged — it gives reviewers helpful context and speeds up the review. You can use any of these keywords:

  • Fixes #<issue_number>
  • Closes #<issue_number>
  • Resolves #<issue_number>
  • Refs #<rfc_or_issue_number> (for RFCs)

🙏 Thanks for helping us keep the project well-organized!

@gemini-code-assist gemini-code-assist Bot 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.

Code Review

This pull request updates the Sparse Flash Attention documentation and kernel implementation to correctly handle empty local sparse rows on Atlas A2/A3, ensuring they return a zero softmax sum and negative infinity LSE. Specifically, it adds a check in the MLA kernel for empty rows under the TND layout with page attention when returning softmax LSE, and introduces comprehensive end-to-end regression tests. The feedback provides a structured PR title and summary in compliance with the repository's style guide.

<td>returnSoftmaxLse</td>
<td>可选属性</td>
<td>用于表示是否返回softmax_max和softmax_sum。True表示返回,False表示不返回,默认值为False。该参数仅在训练且layout_kv不为PA_BSND场景支持。</td>
<td>用于表示是否返回softmax_max和softmax_sum。True表示返回,False表示不返回,默认值为False。支持推理的PA_BSND场景;DCP decode使用softmax_max + log(softmax_sum)合并各rank的局部注意力结果。</td>

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.

high

According to the Repository Style Guide (Pull Request Summary Style Guide), a PR review should include a suggested PR Title and PR Summary formatted in markdown code blocks.

Suggested PR Title:

[Ops][BugFix] Return zero softmax sums for empty A2/A3 DCP rows

Suggested PR Summary:

### What this PR does / why we need it?

This PR fixes an issue where empty local sparse rows in the scoped A2/A3 unquantized SFA kernel return a nonzero softmax sum (e.g., 512) instead of zero, causing `softmax_max + log(softmax_sum)` to be a finite sentinel instead of `-inf` LSE.
It detects empty prefixes in the TND/PA_BSND, token-wise, `sparse_mode=0`, return-LSE path and reuses the existing zero-output path.

### Does this PR introduce _any_ user-facing change?

Yes, empty local sparse rows in the scoped A2/A3 return-LSE path now return zero attention output and zero softmax sum, giving `-inf` when reconstructing LSE. The public signature is unchanged.

### How was this patch tested?

Tested on Ascend 910B3 with CANN 9.1.0. Added regressions in `tests/e2e/nightly/single_node/ops/singlecard_ops/test_sparse_flash_attention_lse_a2_a3.py` covering FP16/BF16, RoPE/NoPE, permuted physical pages, variable local KV lengths, multiple KV tiles, and graph replay.
References
  1. The style guide requires generating a suggested PR Title and PR Summary in markdown code blocks during PR review. (link)

@LiPu-jpg LiPu-jpg changed the title [Bugfix][SFA] Return zero softmax sums for empty A2/A3 DCP rows [BugFix][SFA] Return zero softmax sums for empty A2/A3 DCP rows Sep 30, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation module:tests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant