Repository navigation
Conversation
…ce peak GPU memory
| static bool is_grouped_fc_decompression_param(const ProgramBuilder& p, const std::shared_ptr<ov::op::v0::Constant>& op) { | ||
| const auto& shape = op->get_shape(); | ||
| // Sub-byte zp (u4 / i4) packs two values per byte and keeps the plain layout | ||
| if (!p.use_new_shape_infer() || shape.size() != 2 || shape[1] < 2 || op->get_output_element_type(0).bitwidth() < 8) |
There was a problem hiding this comment.
why check shape[1] < 2, is it skipping dim==1?
There was a problem hiding this comment.
Yes, on purpose. With one group ([N, 1]), bfyx and fbyx have the same memory order, so prepare_quantization doesn’t reorder it either (need_reorder requires groups_count > 1). There’s no second copy to save, so it stays on the normal upload. I will add a comment.
There was a problem hiding this comment.
I think using shape[1]==1 seems better
There was a problem hiding this comment.
Done, changed to shape[1] == 1
| if (!(user.get_index() == 3 ? fc->get_transpose_b_scale() : fc->get_transpose_b_zp())) | ||
| return false; | ||
| const auto& w_shape = fc->get_input_partial_shape(1); | ||
| if (w_shape.rank().is_dynamic() || w_shape.size() != 2 || w_shape[0].is_dynamic() || static_cast<size_t>(w_shape[0].get_length()) != shape[0]) |
There was a problem hiding this comment.
what if broadcasting on dim[1]?
There was a problem hiding this comment.
Broadcast on dim[1] ([N, 1]) is the single-group case above and is skipped. Broadcast on dim[0] ([1, groups]) fails the shape[0] == N check, so it also keeps the plain upload, and prepare_quantization reorders it as it does today. Only exact [N, groups] params take the new path. I will add a comment.
|
build_jenkins |
…n-upload-rebase # Conflicts: # src/plugins/intel_gpu/src/plugin/ops/constant.cpp
| auto groups_count = dep_pshape[groups_idx].get_length(); | ||
| return groups_count > 1; | ||
| // grouped parameters that are already in fbyx (uploaded or computed that way) need no reorder | ||
| return groups_count > 1 && dep_layout.format != format::fbyx; |
There was a problem hiding this comment.
It appears that the fbyx format is not used in all cases, it would be better to skip adding a reorder operation inside reorder_bfyx whenever the current format is already the same as the target format
There was a problem hiding this comment.
Done: reorder_bfyx now returns early when the dependency is already in the target format, and the PR-specific fbyx check in need_reorder is gone.
|
|
||
|
|
||
|
|
||
| // fbyx of [N, groups] is [groups][N] in memory |
There was a problem hiding this comment.
It looks like this breaks the weightless cache path
There was a problem hiding this comment.
You were right: on the previous version, 24 of 96 weightless cache cases with grouped parameters gave wrong results (new test: MatmulGroupedWeightsDecompression).
The constant.cpp change is removed. The new transformation records the transpose, and the weightless cache manager replays it on load. All 96 cases pass, and the cache blob stays the same size as on master.
One side finding: the weightless tests can't detect a failed cache import, because the core silently recompiles and the output is still correct. I hit this myself and only caught it by checking ov::loaded_from_cache. Should the shared test helper assert ov::loaded_from_cache? I left it out because it changes the common test body.
|
|
||
| // Scale/ZP [N, groups] used only by FullyConnectedCompressed with transposed 2D weights (transpose_b_scale / transpose_b_zp). | ||
| // prepare_quantization reorders exactly these to fbyx. | ||
| static bool is_grouped_fc_decompression_param(const ProgramBuilder& p, const std::shared_ptr<ov::op::v0::Constant>& op) { |
There was a problem hiding this comment.
[Globaly] If the issue of excessive memory consumption stems from redundant weight copying - caused by reordering and the associated computations within propagate_constants - then the best solution is to address the root cause of the inefficiency in propagate_constants. Currently, it creates numerous small constant subgraphs, each with its own unique buffer. Ideally, this should be refactored to reuse a single, pre-allocated, maximum-sized buffer. But this is a separate large work
Rather than the current approach, a potentially better generalized solution would be to avoid making such changes here. Instead, you can create a new transformation - to be applied after all transformations related to the FC layer. Use the logic from your is_grouped_fc_decompression_param in pattern matcher, and modify transpose_b_scale and transpose_b_zp from true to false with const data modification. prepare_quantization should not use reorder in this case, and convert_decompression_tensor should handle it w/o perf affect
There was a problem hiding this comment.
Done: TransposeFCDecompressionParams, placed after DynamicQuantizeFullyConnected and before EliminatePad. It transposes [N, groups] scales and zero points (more than one group, at least 8 bits) and clears transpose_b_scale / transpose_b_zp. A shared constant gets its own transposed copy for the FC, and its other users keep the original. Updated numbers are in the description.
…transposed constants
|
Measured on Intel Arc B390 with four Q4_K_M models (Llama-3.2 1B / Gemma-4 E2B / Qwen3.5 4B / Phi-4 mini):
@Lyamin-Roman what would be the better way?
I am happy to help with the refactor either way. |
Details:
Reduce peak GPU memory for compressed LLMs by storing grouped weight decompression scales and zero points of
FullyConnectedCompressedin the[groups, N]layout at compile time, soprepare_quantizationno longer adds areorder that keeps a second copy of each parameter in device memory during program build.
Implementation
TransposeFCDecompressionParams(registered after the FullyConnected passes, beforeEliminatePad): forFullyConnectedCompressedwith transposed 2D weights[N, K], transposes[N, groups]scale andzero-point constants to
[groups, N]and setstranspose_b_scale/transpose_b_zpto false. The OCL kernels(
convert_decompression_tensor) and oneDNN (get_ifm_dim_idx) already handle this layout, andoptimize_weights_decompression_parametersthen adds no reorder.WeightlessCacheAttributeof the original one and records thetranspose in the
gpu_weightless_transpose_orderrt_info entry.weightless_cache_managerstores it and replays thetranspose after loading the original data from the weights file, so the cache blob does not grow.
prepare_quantization.cpp:reorder_bfyxreturns early when the parameter is already in the target format.plugin/ops/constant.cppis removed.GPU memory
Measured on Windows, Intel Arc B390 (Panther Lake), OV master 1acdfcd, llama.cpp OpenVINO backend, Q4_K_M,
llama-bench -p 0 -n 128 -d 4096; before = the same build with the new pass disabled.Duplicate parameter copies during program build (GPU plugin allocation counters in
propagate_constants):Peak process GPU memory (dedicated + shared, two runs each, within 1 MB of each other):
With 4096 tokens of context, the runtime allocations of Llama-3.2 1B and Phi-4 mini exceed their compile-time peak, so
the process peak drops less than the compile-time saving for those two models.
Correctness is unchanged: KL divergence against the CPU backend (wikitext-2, 20 x 512 tokens), before / after:
Llama 0.051698 / 0.051698, Gemma 0.330667 / 0.330667, Qwen 0.573779 / 0.573851 (± 0.0126), Phi-4 0.048667 / 0.048667.
Performance is unchanged within noise.
llama-bench -p 4096 -n 128 -d 0,4096 -r 3, mean of two rounds, tokens/s(before → after):
Decode is within 1% everywhere. Prefill differences are within run-to-run variation on this machine (the two rounds
differ by up to 9% for Llama prefill and up to 20% for Phi-4 prefill at depth 4096).
Tests
transpose_fc_decompression_params_test.cpp): scale with and without zero point(u8 / f16 / u4), single group, broadcast
[1, groups], already[groups, N], non-constant scale, a scale shared by twoFCs and a
Multiply, and the weightless cache attribute plus transpose record.cache_serialization.weightless_transpose_is_replayed_on_load): f32 / f16 sourcedata, including a model constant of another rank (
[N, groups, 1], as before the FC compression reshape).CheckWeightlessCacheAccuracyLowPrecision.MatmulGroupedWeightsDecompression): grouped scale and zeropoint with transposed weights for all import APIs and cache modes. On the previous version of this PR, 24 of 96 cases
gave wrong results; with the transpose replay disabled, 48 of 96 fail.
*CheckWeightlessCacheAccuracy*:*MatMulCompressedWeights*:*MatMulSharedCompressedWeights*(Intel ArcB580, Windows, after merging master 3d39b76): 1,839 passed; the 6 failures
(
smoke_MatMulCompressedWeights_extra_multiply, u4 / i4, group size 2, andsmoke_MatMulCompressedWeights_basic,u2, f16 activations) also fail with this PR's changes disabled on this device.
Reviewer notes
propagate_constants: the root cause of the duplicate copies is that it builds all constant subgraphs in onenetwork with separate output buffers. A refactor to reuse one buffer would cover every constant, not only FC scales
and zero points; this PR keeps to the FC case (see the discussion below).
dep_layout.format != format::fbyxcheck toneed_reorder. With thesame-format skip in
reorder_bfyxfrom this PR, that check is no longer needed; I will remove it from [GPU] Use oneDNN static_sbzp for exact f16/bf16 decompression zero points #38696 once oneof them merges. The edits are in different lines, so they merge cleanly in either order.
Tickets:
AI Assistance: