Improve MLAS NCHWc conv thread utilization via cost-weighted work par… - #31660
Open
mirounga wants to merge 2 commits into
Open
Improve MLAS NCHWc conv thread utilization via cost-weighted work par…#31660mirounga wants to merge 2 commits into
mirounga wants to merge 2 commits into
Conversation
…titioning
The NCHWc convolution algorithms (pointwise and direct) partition work
uniformly by item index over FilterSetCount x OutputHeight items, but the
cost of an item is proportional to its FilterSet's FilterCount: full sets
process FilterSetSize (4) NCHWc blocks while a ragged last set (when
Cout/16 % 4 != 0) processes as few as 1. Threads landing on full sets do
up to 4x the FLOPs of threads on the tail set, capping scheduling
efficiency at 62-75% for common shapes (e.g. Cout=96: sets of 4+2 blocks
-> 0.75 efficiency at 4 threads).
Replace the uniform index split with a split proportional to FLOP cost,
measured in block-rows (one NCHWc output block x one output row):
MlasPartitionWork distributes cost intervals, and CostToWorkIndex maps
cost boundaries back to work-item boundaries with exact coverage and no
overlap. Applied to both MLAS_NCHWC_CONV_POINTWISE_ALGORITHM and
MLAS_NCHWC_CONV_NCHWC_ALGORITHM; the NCHW first-layer, depthwise, and
pooling algorithms keep the uniform split (no imbalance there).
Outputs are bitwise identical: repartitioning moves whole output-row
items between threads without changing any element's accumulation order.
Also:
- Add session config "mlas.nchwc_conv_max_input_channel_batch" to
override the pointwise algorithm's input-channel batch (default 128,
rounded up to a BlockSize multiple; 0/unset keeps the default) via
MLAS_BACKEND_KERNEL_SELECTOR_CONFIG, for perf experimentation.
- Fix copy-paste guards in the SconvKernelAvx512F.S ReLU post-process
(FilterCount 2 -> 3/4 for the zmm14/18/22 and zmm15/19/23 rows);
behavior was accidentally correct but the FilterCount=2/OutputCount=6
variant emitted 6 vmaxps on dead registers.
Measured on AMD Ryzen AI MAX+ 395 (Zen 5, AVX-512), batch=1 fp32,
intra_op=4, 512-iteration medians vs unmodified main:
yolox_tiny 8.009 -> 7.149 ms (-10.7%) 3x3-conv heavy, 86% of its
3x3 time in ragged shapes
mv3_large 0.962 -> 0.922 ms (-4.2%)
mv3_small 0.458 -> 0.455 ms (-1% noise)
mobileclip unchanged (+/-1% noise; its conv shapes are 64-aligned)
Hottest yolox shape (96->96 3x3 @52x52): 939 -> 1167 GFLOP/s (91% of
machine peak). All 29,910 onnxruntime_mlas_test cases pass.
Contributor
There was a problem hiding this comment.
Pull request overview
This PR improves CPU MLAS NCHWc convolution thread utilization by switching from uniform work-item partitioning to a cost-weighted partitioning scheme, reducing load imbalance for ragged last filter sets. It also adds a new session config knob to tune the pointwise (1x1) NCHWc input-channel batching behavior for performance experimentation.
Changes:
- Implement cost-weighted work partitioning for MLAS NCHWc direct and pointwise convolution algorithms via
CostToWorkIndex+PrepareWorkWeighted. - Add session config key
mlas.nchwc_conv_max_input_channel_batchand plumb it into MLAS backend kernel selector config parsing. - Use the new config value to override (and block-size-round) the pointwise conv max input-channel batch (default remains 128).
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
| onnxruntime/core/providers/cpu/mlas_backend_kernel_selector_config_utils.h | Parse the new NCHWc pointwise tuning option from session config into MLAS selector config. |
| onnxruntime/core/mlas/lib/snchwc.cpp | Add cost-weighted partitioning for NCHWc conv work distribution and apply configurable pointwise input-channel batching. |
| onnxruntime/core/mlas/inc/mlas.h | Extend MLAS_BACKEND_KERNEL_SELECTOR_CONFIG with nchwc_conv_max_input_channel_batch. |
| include/onnxruntime/core/session/onnxruntime_session_options_config_keys.h | Define and document the new session option key mlas.nchwc_conv_max_input_channel_batch. |
Suppressed comments (1)
onnxruntime/core/mlas/lib/snchwc.cpp:723
PrepareWorkWeightedcallsSeekToWork(WorkIndexBegin)even whenCostCount == 0, which can happen whenTotalCost < tids. In that caseWorkIndexBeginmay equalTotalWork, andSeekToWorkwill advance pointers past the end of the input/output/filter buffers (undefined pointer arithmetic). Only seek when there is actual work to execute.
WorkRemaining = WorkIndexEnd - WorkIndexBegin;
SeekToWork(WorkIndexBegin);
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
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.
…titioning
The NCHWc convolution algorithms (pointwise and direct) partition work uniformly by item index over FilterSetCount x OutputHeight items, but the cost of an item is proportional to its FilterSet's FilterCount: full sets process FilterSetSize (4) NCHWc blocks while a ragged last set (when Cout/16 % 4 != 0) processes as few as 1. Threads landing on full sets do up to 4x the FLOPs of threads on the tail set, capping scheduling efficiency at 62-75% for common shapes (e.g. Cout=96: sets of 4+2 blocks -> 0.75 efficiency at 4 threads).
Replace the uniform index split with a split proportional to FLOP cost, measured in block-rows (one NCHWc output block x one output row): MlasPartitionWork distributes cost intervals, and CostToWorkIndex maps cost boundaries back to work-item boundaries with exact coverage and no overlap. Applied to both MLAS_NCHWC_CONV_POINTWISE_ALGORITHM and MLAS_NCHWC_CONV_NCHWC_ALGORITHM; the NCHW first-layer, depthwise, and pooling algorithms keep the uniform split (no imbalance there).
Outputs are bitwise identical: repartitioning moves whole output-row items between threads without changing any element's accumulation order.
Also:
Measured on AMD Ryzen AI MAX+ 395 (Zen 5, AVX-512), batch=1 fp32, intra_op=4, 512-iteration medians vs unmodified main:
yolox_tiny 8.009 -> 7.149 ms (-10.7%) 3x3-conv heavy, 86% of its
3x3 time in ragged shapes
mv3_large 0.962 -> 0.922 ms (-4.2%)
mv3_small 0.458 -> 0.455 ms (-1% noise)
mobileclip unchanged (+/-1% noise; its conv shapes are 64-aligned)
Hottest yolox shape (96->96 3x3 @52x52): 939 -> 1167 GFLOP/s (91% of machine peak). All 29,910 onnxruntime_mlas_test cases pass.
Description
Motivation and Context