Skip to content

cuda: fix MSVC duplicate-COMDAT link failure from twice-nested retired_buf - #355

Merged
TheTom merged 1 commit into
TheTom:feature/turboquant-kv-cachefrom
tmark00:fix/msvc-duplicate-comdat-retired-buf
Sep 6, 2026
Merged

cuda: fix MSVC duplicate-COMDAT link failure from twice-nested retired_buf#355
TheTom merged 1 commit into
TheTom:feature/turboquant-kv-cachefrom
tmark00:fix/msvc-duplicate-comdat-retired-buf

Conversation

@tmark00

@tmark00 tmark00 commented Sep 5, 2026

Copy link
Copy Markdown

What

feature/turboquant-kv-cache at 80007e715 does not link on Windows/MSVC. q8_cache and
tq_rot_cache are unnamed member structs of ggml_backend_cuda_context, and each nested its own
identical struct retired_buf:

    struct {
        ...
        struct retired_buf { char * ptr; size_t cap; int dev; };
        std::vector<retired_buf> retired;
    } q8_cache;

    struct {
        ...
        struct retired_buf { char * ptr; size_t cap; int dev; };   // same again
        std::vector<retired_buf> retired;
    } tq_rot_cache;

MSVC decorates a type nested in an unnamed struct as <unnamed-tag>, so both nested types mangle
to the same decorated name. The two std::vector<retired_buf> instantiations then emit COMDAT
sections with identical names into a single ggml-cuda.cu.obj, and link.exe rejects the object:

ggml-cuda.cu.obj : fatal error LNK1179: invalid or corrupt file: duplicate COMDAT
'??$?0$$V@?$_Compressed_pair@V?$allocator@Uretired_buf@<unnamed-tag>@ggml_backend_cuda_context@@@std@@
V?$_Vector_val@U?$_Simple_types@Uretired_buf@<unnamed-tag>@ggml_backend_cuda_context@@@std@@@2@$00@std@@
QEAA@U_Zero_then_variadic_args_t@1@@Z'

Because ninja deletes the target before linking, a failed build also leaves the tree without
ggml-cuda.dll, so an incremental rebuild on Windows takes the previously working binary with it.

Fix

The two definitions are identical, so hoist a single retired_buf to class scope. As a member of
a named class it mangles unambiguously, and the duplication goes away as a side effect. Every use
is either auto or braced initialisation (ggml-cuda.cu:751-762, mmvq.cu:1300,
mmvq-tq.cu:927), so no call site changes.

Who introduced it

Neither parent is at fault alone: 767b6278e added the q8 quantize cache and 3df06b110 the TQ
rotation cache, each with its own nested copy. The collision only exists once both are in the same
translation unit.

Verification

Windows 11, MSVC 14.51, CUDA sm_120 (RTX 5090 Laptop), Ninja, Release, BUILD_SHARED_LIBS=ON,
CMAKE_CUDA_ARCHITECTURES=120-real:

  • 80007e715 unmodified: FAILED: bin/ggml-cuda.dll, LNK1179 as above
  • 80007e715 + this commit: full cmake --build completes, 0 errors, ggml-cuda.dll produced

Non-MSVC toolchains were not affected to begin with — GCC and Clang give the nested types distinct
mangled names — so this should be a no-op there.

🤖 Generated with Claude Code

…d_buf

q8_cache and tq_rot_cache are unnamed member structs of ggml_backend_cuda_context,
and each nested its own identical `struct retired_buf`. MSVC decorates a type
nested in an unnamed struct as <unnamed-tag>, so both nested types mangle to the
same decorated name and the two std::vector<retired_buf> instantiations emit
COMDAT sections with identical names into a single ggml-cuda.cu.obj. link.exe
rejects the object:

  ggml-cuda.cu.obj : fatal error LNK1179: invalid or corrupt file: duplicate
  COMDAT '??$?0$$V@?$_Compressed_pair@V?$allocator@Uretired_buf@<unnamed-tag>@
  ggml_backend_cuda_context@@@std@@...'

The two definitions are identical, so hoist a single retired_buf to class scope,
where it is a member of a named class and mangles unambiguously. Every use is
either `auto` or braced initialisation, so no call site changes.

Neither parent commit is at fault on its own: 767b627 added the q8 cache and
3df06b1 the TQ rotation cache, and the collision only appears once both are in
the same translation unit.

Verified by building the full CUDA target on Windows with MSVC 14.51 and CUDA
sm_120 (Ninja, Release, BUILD_SHARED_LIBS=ON): fails to link at 80007e7 and
links cleanly with this change.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@TheTom

TheTom commented Sep 6, 2026

Copy link
Copy Markdown
Owner

Thanks, and sorry: that nesting came from my fix on #343, and the hosted Windows jobs build with clang so they never saw it. Hoisting the one retired_buf to class scope is the right fix. CI was waiting on first-contributor approval, now running; merging when it is green.

@TheTom
TheTom merged commit 407f323 into TheTom:feature/turboquant-kv-cache Sep 6, 2026
5 of 19 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants