optimize int4 prepacking of the weight on CPU - #31690
Conversation
There was a problem hiding this comment.
Pull request overview
This PR aims to reduce CPU session initialization time for int4-quantized models by lowering threadpool scheduling overhead during QNBit weight prepacking (MLAS SQNBit GEMM packing).
Changes:
- Introduces chunked (coarser-grained) parallelization for
PackQuantBandQ8PackQuantBto reduce the number of threadpool iterations. - Applies similar chunking to block-sum/scale reordering (
ComputePackBlkSum,Q8ComputePackBlkSum) and attempts to remove an intermediate scale copy.
Suppressed comments (4)
onnxruntime/core/mlas/lib/sqnbitgemm_kernel_avx_common.h:285
ChunkCountis computed withMlasDivRoundup(SubBlkCountK, ChunkSubBlks)whereChunkSubBlks = min(..., SubBlkCountK). IfSubBlkCountKis 0 (possible whenStrideNis 0 becauseBlockCountK/Kis 0), this is a divide-by-zero. Add an early return whenSubBlkCountK==0(and optionallyN==0) before calculating chunk sizes.
const size_t StrideN = BlockCountK * BlkLen;
const size_t BlkSize = MlasQNBitBlkDataSizeInBytes(BlkBitWidth, BlkLen);
const size_t SubBlkSize = MlasQNBitBlkDataSizeInBytes(BlkBitWidth, SubBlkLen);
const size_t SubBlkCountK = MlasDivRoundup(StrideN, SubBlkLen);
const size_t RemainderBlockCountK = BlockCountK % (SubBlkLen > BlkLen ? SubBlkLen / BlkLen : 1);
// OPTIMIZATION: Coarser-grained parallelization for Q8PackQuantB too
const size_t ChunkSubBlks = std::min(MLAS_PACK_BLKS_PER_CHUNK, SubBlkCountK);
const size_t ChunkCount = MlasDivRoundup(SubBlkCountK, ChunkSubBlks);
const size_t Iterations = N * ChunkCount; // Reduced from N * SubBlkCountK
onnxruntime/core/mlas/lib/sqnbitgemm_kernel_avx_common.h:355
ComputePackBlkSumreordersQuantBScaleBeginin-place usingGetContinueLayoutOffset*(which interleaves acrossn), so reading scales directly fromQuantBScaleBeginwhile also writing reordered scales can corrupt the source values (and races across threads). The previous defensive copy was needed for correctness; please restore a stable source buffer (and add aBlockCountK==0guard to avoid divide-by-zero when chunking).
MlasTrySimpleParallel(ThreadPool, TotalIterations, [&](ptrdiff_t tid) {
const size_t n = tid / ChunkCount;
const size_t chunk_idx = tid % ChunkCount;
const size_t k_blk_start = chunk_idx * ChunkBlks;
const size_t k_blk_end = std::min(k_blk_start + ChunkBlks, BlockCountK);
onnxruntime/core/mlas/lib/sqnbitgemm_kernel_avx_common.h:408
Q8ComputePackBlkSumreordersQuantBScaleBeginin-place (across groups of 4 columns). Reading scales directly fromQuantBScaleBeginwhile writing reordered scales can corrupt the source values (and is unsafe under parallel execution). Restore a stable source copy (and add an early return forBlockCountK==0to avoid divide-by-zero in chunk sizing).
// OPTIMIZATION: Avoid unnecessary copy - read directly from source
// Pre-compute invariants to avoid redundant calculations in loop
const int blks_per_sub = (BlkLen < SubBlkLen) ? (int)(SubBlkLen / BlkLen) : 0;
const size_t sub_blk_count_k = (blks_per_sub > 0) ? MlasDivRoundup(BlockCountK, blks_per_sub) : 0;
const size_t remainder_blk = (blks_per_sub > 0) ? (BlockCountK % blks_per_sub) : 0;
onnxruntime/core/mlas/lib/sqnbitgemm_kernel_avx_common.h:431
- After restoring
QuantBScaleBeginCopy, this loop should read from the copy (stable source) rather than from the in-place destination buffer being concurrently rewritten.
for (size_t k_blk = k_blk_start; k_blk < k_blk_end; ++k_blk) {
// READ scales directly, avoiding copy
const float QuantBScale = QuantBScaleBegin[n * BlockCountK + k_blk];
uint8_t zp = 128;
Co-authored-by: xadupre <22452781+xadupre@users.noreply.github.com>
|
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
Copilot update with main branch, does the job failing have a history of recent failures? |
Co-authored-by: xadupre <22452781+xadupre@users.noreply.github.com>
Merged Yes, the failing job has recent failure history: |
I investigated run
This matches prior failures on this PR ( |
|
Copilot fix build |
Done — the Linux CI build is now green on this branch. After updating with |
Move boundary dimensions and options from template parameters to runtime values to avoid excessive compiler specialization. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Use explicit runtime dimension conversions for the blockwise quantization helpers so macOS Clang builds cleanly with shortening warnings treated as errors. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
onnxruntime struggle to load qwen3.6-27B-int4 quantized with ModelBuilder.
This PR improves the parallelization of the prepacking.
It improves the creation of the session by 25%. Processor is Intel(R) Xeon(R) Platinum 8480C.
onnxruntime.InferenceSession(<model>, provides=["CPUExecutionProvider"])Before:
After: