2626
2727namespace kv_cache_manager {
2828
29+ #if defined(USING_CUDA) || defined(USING_MUSA)
30+ namespace {
31+
32+ bool IsChecksumHashableBlock (const BlockBuffer &block_buffer) {
33+ if (block_buffer.iovs .empty ()) {
34+ return false ;
35+ }
36+ return std::all_of (block_buffer.iovs .begin (), block_buffer.iovs .end (), [](const Iov &iov) {
37+ return !iov.ignore && iov.base != nullptr && iov.size > 0 ;
38+ });
39+ }
40+
41+ bool HashBlocksByIovShape (const BlockBuffers &block_buffers,
42+ SdkBufferCheckPool::CellHandle &handle,
43+ size_t max_check_iov_num,
44+ std::vector<int64_t > &out_checksums) {
45+ out_checksums.clear ();
46+ out_checksums.resize (block_buffers.size ());
47+ if (block_buffers.empty ()) {
48+ return true ;
49+ }
50+
51+ BlockBuffers chunk;
52+ std::vector<size_t > chunk_indices;
53+ size_t chunk_iov_num = 0 ;
54+ size_t chunk_total_iovs = 0 ;
55+
56+ auto flush_chunk = [&]() -> bool {
57+ if (chunk.empty ()) {
58+ return true ;
59+ }
60+ auto checksums = SdkBufferCheckUtil::GetBlocksHash (
61+ chunk, handle->d_iovs , handle->d_crcs , handle->h_iovs , max_check_iov_num, handle->gpu_stream );
62+ if (checksums.size () != chunk.size ()) {
63+ KVCM_LOG_ERROR (" checksum hash returned [%zu] entries for [%zu] blocks" , checksums.size (), chunk.size ());
64+ return false ;
65+ }
66+ for (size_t i = 0 ; i < checksums.size (); ++i) {
67+ out_checksums[chunk_indices[i]] = checksums[i];
68+ }
69+ chunk.clear ();
70+ chunk_indices.clear ();
71+ chunk_iov_num = 0 ;
72+ chunk_total_iovs = 0 ;
73+ return true ;
74+ };
75+
76+ for (size_t i = 0 ; i < block_buffers.size (); ++i) {
77+ const size_t iov_num = block_buffers[i].iovs .size ();
78+ if (iov_num == 0 || iov_num > max_check_iov_num) {
79+ KVCM_LOG_ERROR (" block [%zu] has invalid iov_num [%zu] for checksum hash" , i, iov_num);
80+ return false ;
81+ }
82+ if (!chunk.empty () && (iov_num != chunk_iov_num || chunk_total_iovs + iov_num > max_check_iov_num)) {
83+ if (!flush_chunk ()) {
84+ return false ;
85+ }
86+ }
87+ if (chunk.empty ()) {
88+ chunk_iov_num = iov_num;
89+ }
90+ chunk.push_back (block_buffers[i]);
91+ chunk_indices.push_back (i);
92+ chunk_total_iovs += iov_num;
93+ }
94+ return flush_chunk ();
95+ }
96+
97+ } // namespace
98+ #endif
99+
29100TransferClientImpl::TransferClientImpl () {}
30101
31102TransferClientImpl::~TransferClientImpl () {}
@@ -237,66 +308,66 @@ ClientErrorCode TransferClientImpl::LoadKvCaches(const UriStrVec &uri_str_vec,
237308 KVCM_LOG_WARN (" expected_checksums given but sdk_buffer_check_pool is not enabled; "
238309 " skip verification" );
239310 } else {
240- std::vector<int64_t > effective_expected = *expected_checksums;
311+ BlockBuffers verifiable_block_buffers;
312+ std::vector<int64_t > verifiable_expected;
313+ std::vector<size_t > original_indices;
314+ verifiable_block_buffers.reserve (block_buffers.size ());
315+ verifiable_expected.reserve (block_buffers.size ());
316+ original_indices.reserve (block_buffers.size ());
241317 size_t skipped_blocks = 0 ;
242318 for (size_t i = 0 ; i < block_buffers.size (); ++i) {
243- if (effective_expected [i] == 0 ) {
319+ if ((*expected_checksums) [i] == 0 ) {
244320 continue ; // already sentinel
245321 }
246- const auto &iovs = block_buffers[i].iovs ;
247- bool unverifiable = iovs.empty ();
248- if (!unverifiable) {
249- for (const auto &iov : iovs) {
250- if (iov.ignore ) {
251- unverifiable = true ;
252- break ;
253- }
254- }
255- }
256- if (unverifiable) {
257- effective_expected[i] = 0 ;
322+ if (!IsChecksumHashableBlock (block_buffers[i])) {
258323 ++skipped_blocks;
324+ continue ;
259325 }
326+ verifiable_block_buffers.push_back (block_buffers[i]);
327+ verifiable_expected.push_back ((*expected_checksums)[i]);
328+ original_indices.push_back (i);
260329 }
261330 if (skipped_blocks > 0 ) {
262- KVCM_LOG_DEBUG (" checksum verification skipped %zu block(s) due to ignored or empty iovs" ,
331+ KVCM_LOG_DEBUG (" checksum verification skipped %zu block(s) due to ignored, empty, or invalid iovs" ,
263332 skipped_blocks);
264333 }
265334 // Short-circuit when nothing is left to verify (all-legacy batch or every
266335 // block downgraded to sentinel by the partial-read / empty-iov handling
267336 // above). Avoids one wasted GPU hash compute and — more importantly —
268337 // avoids GetBlocksHash dereferencing block_buffers on shapes it cannot
269338 // hash (empty iovs, etc.) after we already declared them unverifiable.
270- const bool has_any_expected =
271- std::any_of (effective_expected.begin (), effective_expected.end (), [](int64_t v) { return v != 0 ; });
272- if (!has_any_expected) {
339+ if (verifiable_expected.empty ()) {
273340 KVCM_LOG_DEBUG (" checksum verification: no non-sentinel expected entries; skip" );
274341 return ec;
275342 }
276343 auto handle = sdk_buffer_check_pool_->GetCell ();
277- auto actual = SdkBufferCheckUtil::GetBlocksHash (
278- block_buffers, handle->d_iovs , handle->d_crcs , handle->h_iovs , max_check_iov_num_, handle->gpu_stream );
344+ std::vector<int64_t > actual;
345+ if (!HashBlocksByIovShape (verifiable_block_buffers, handle, max_check_iov_num_, actual)) {
346+ KVCM_LOG_ERROR (" checksum verification failed to hash verifiable blocks safely" );
347+ return ER_CHECKSUM_MISMATCH ;
348+ }
279349 const bool strict_mode = EnvUtil::GetEnv (" KVCM_CHECKSUM_STRICT_MODE" , false );
280- const auto verify_result = VerifyBatchChecksums (effective_expected , actual, strict_mode);
350+ const auto verify_result = VerifyBatchChecksums (verifiable_expected , actual, strict_mode);
281351 if (verify_result.mismatch ) {
282352 if (verify_result.faulty_indices .empty ()) {
283353 // Size mismatch surfaced from the helper (we already pre-checked
284354 // expected vs block_buffers above, so this is paranoid coverage).
285355 KVCM_LOG_ERROR (
286- " actual checksums size [%zu] != expected [%zu]" , actual.size (), effective_expected .size ());
356+ " actual checksums size [%zu] != expected [%zu]" , actual.size (), verifiable_expected .size ());
287357 } else {
288358 // Structured per-block log; field set mirrors ChecksumMismatchEvent
289359 // so a log scraper can build the same observability surface until
290360 // the client SDK grows a real EventManager hook (follow-up).
291- for (auto idx : verify_result.faulty_indices ) {
361+ for (auto compact_idx : verify_result.faulty_indices ) {
362+ const size_t idx = original_indices[compact_idx];
292363 const char *block_id_str = (trace_info != nullptr && idx < trace_info->block_ids .size ())
293364 ? trace_info->block_ids [idx].c_str ()
294365 : " <unknown>" ;
295366 KVCM_LOG_ERROR (" ChecksumMismatchEvent {block_index=%zu, expected_checksum=0x%lx, "
296367 " actual_checksum=0x%lx, storage_uri=\" %s\" , block_id=\" %s\" }" ,
297368 idx,
298- static_cast <unsigned long >(effective_expected[idx ]),
299- static_cast <unsigned long >(actual[idx ]),
369+ static_cast <unsigned long >(verifiable_expected[compact_idx ]),
370+ static_cast <unsigned long >(actual[compact_idx ]),
300371 idx < uri_str_vec.size () ? uri_str_vec[idx].c_str () : " <oob>" ,
301372 block_id_str);
302373 }
@@ -326,21 +397,13 @@ std::pair<ClientErrorCode, UriStrVec> TransferClientImpl::SaveKvCaches(const Uri
326397 // request or the legacy KVCM_SDK_CHECK print-only fallback. The two paths share
327398 // the same computation so the checksum is computed at most once per call.
328399 //
329- // SdkBufferCheckUtil::GetBlocksHash dereferences block_buffers.front(), so we
330- // must guard against empty / malformed input here. The full URI / Iov validity
331- // check still happens inside sdk_wrapper_->Put() below; we only short-circuit
332- // the obviously invalid shapes that would crash the hashing kernel.
400+ // SdkBufferCheckUtil::GetBlocksHash dereferences block_buffers.front() and
401+ // assumes uniform iov shapes within one call. Validate every block first and
402+ // then hash by compatible iov-shape chunks.
333403 std::vector<int64_t > block_checksums;
334404 bool block_checksums_computed = false ;
335- const bool checksum_input_usable = !block_buffers.empty () && !block_buffers.front ().iovs .empty ();
336405 if (out_checksums != nullptr || is_check_buffer_) {
337- if (!checksum_input_usable) {
338- if (out_checksums != nullptr ) {
339- KVCM_LOG_WARN (" block_buffers empty or first block has no iovs; "
340- " skip checksum compute and return empty out_checksums" );
341- out_checksums->clear ();
342- }
343- } else if (!sdk_buffer_check_pool_) {
406+ if (!sdk_buffer_check_pool_) {
344407 if (out_checksums != nullptr ) {
345408 KVCM_LOG_WARN (" out_checksums requested but sdk_buffer_check_pool is not enabled; "
346409 " return empty vector (caller should treat as 'checksum not available')" );
@@ -349,30 +412,40 @@ std::pair<ClientErrorCode, UriStrVec> TransferClientImpl::SaveKvCaches(const Uri
349412 } else {
350413 bool need_print = (trace_info == nullptr ) ? true : trace_info->need_print ;
351414 if (out_checksums != nullptr || need_print) {
352- auto handle = sdk_buffer_check_pool_->GetCell ();
353- block_checksums = SdkBufferCheckUtil::GetBlocksHash (block_buffers,
354- handle->d_iovs ,
355- handle->d_crcs ,
356- handle->h_iovs ,
357- max_check_iov_num_,
358- handle->gpu_stream );
359- block_checksums_computed = true ;
360- // GetBlocksHash caps at max_check_iov_num_ and silently returns a
361- // shorter vector; if the caller wants checksums we cannot let the
362- // write proceed with a length that will fail FinishWrite's per-
363- // location check after the data has already been persisted.
364- if (out_checksums != nullptr && block_checksums.size () != block_buffers.size ()) {
365- KVCM_LOG_ERROR (" block_checksums size [%zu] != block_buffers size [%zu]; iov count likely "
366- " exceeded max_check_iov_num_ [%d]; reject write before it commits" ,
367- block_checksums.size (),
368- block_buffers.size (),
369- max_check_iov_num_);
415+ auto invalid_it =
416+ std::find_if (block_buffers.begin (), block_buffers.end (), [](const BlockBuffer &block_buffer) {
417+ return !IsChecksumHashableBlock (block_buffer);
418+ });
419+ if (invalid_it != block_buffers.end ()) {
420+ const size_t idx = std::distance (block_buffers.begin (), invalid_it);
421+ KVCM_LOG_WARN (" block [%zu] has ignored, empty, null, or zero-size iovs; skip checksum hash" , idx);
422+ if (out_checksums != nullptr ) {
423+ out_checksums->clear ();
424+ return {ER_INVALID_PARAMS , {}};
425+ }
426+ } else {
427+ auto handle = sdk_buffer_check_pool_->GetCell ();
428+ if (!HashBlocksByIovShape (block_buffers, handle, max_check_iov_num_, block_checksums)) {
429+ if (out_checksums != nullptr ) {
430+ out_checksums->clear ();
431+ return {ER_INVALID_PARAMS , {}};
432+ }
433+ } else {
434+ block_checksums_computed = true ;
435+ }
436+ }
437+ if (out_checksums != nullptr && block_checksums_computed &&
438+ block_checksums.size () != block_buffers.size ()) {
439+ KVCM_LOG_ERROR (
440+ " block_checksums size [%zu] != block_buffers size [%zu]; reject write before it commits" ,
441+ block_checksums.size (),
442+ block_buffers.size ());
370443 out_checksums->clear ();
371444 return {ER_INVALID_PARAMS , {}};
372445 }
373446 }
374447 }
375- if (is_check_buffer_ && checksum_input_usable ) {
448+ if (is_check_buffer_ && block_checksums_computed ) {
376449 PrintBlockChecksumAndUri (" put_" , uri_str_vec, block_checksums, trace_info);
377450 }
378451 // Deliberately DO NOT assign to *out_checksums here — see below. A prior
@@ -451,4 +524,4 @@ std::unique_ptr<TransferClient> TransferClient::Create(const std::string &client
451524#undef DEFER
452525#undef CHECK_SDK_BASE
453526#undef CHECK_SDK
454- #undef CHECK_SDK_WITH_TYPE
527+ #undef CHECK_SDK_WITH_TYPE
0 commit comments