Describe the bug
cudf::pack followed by cudf::unpack on a table containing a DICTIONARY32 column does not raise an error, but the unpacked column's keys child has the parent's size (e.g. 5 keys become 100,000), so the column is invalid. contiguous_split has a CUDF_FAIL("Unsupported type") overload for dictionary32, but it is never selected.
Steps/Code to reproduce bug
#include <cudf/column/column_factories.hpp>
#include <cudf/contiguous_split.hpp>
#include <cudf/dictionary/dictionary_column_view.hpp>
#include <cudf/dictionary/encode.hpp>
#include <cudf/table/table_view.hpp>
#include <cuda_runtime.h>
#include <cstdio>
#include <vector>
int main() {
int n = 100000;
std::vector<int32_t> h(n);
for (int i = 0; i < n; ++i) h[i] = i % 5;
auto plain = cudf::make_numeric_column(cudf::data_type{cudf::type_id::INT32}, n);
cudaMemcpy(plain->mutable_view().data<int32_t>(), h.data(), n * 4, cudaMemcpyHostToDevice);
auto dict = cudf::dictionary::encode(*plain); // INT32 indices, 5 keys
std::printf("before: size=%d keys=%d\n", dict->size(), cudf::dictionary_column_view(*dict).keys_size());
auto packed = cudf::pack(cudf::table_view{{*dict}});
auto tv = cudf::unpack(packed);
std::printf("after : size=%d keys=%d\n", tv.column(0).size(), cudf::dictionary_column_view(tv.column(0)).keys_size());
}
Output:
before: size=100000 keys=5
after : size=100000 keys=100000
Expected behavior
Either a round trip that preserves the dictionary (keys child of size 5), or an explicit cudf::logic_error ("Unsupported type") as the code intends — not a silently malformed column.
Environment
- libcudf 26.08.00 (aarch64, CUDA 13.3). Unchanged on
main (d45003e0d0): cpp/src/copying/contiguous_split.cu lines 486-514.
Root cause (analysis)
buf_info_functor has an unconstrained primary template
template <typename T>
std::pair<src_buf_info*, size_type> operator()(column_view const& col, src_buf_info* current, int, int, int, rmm::cuda_stream_view)
and a constrained variadic overload meant for dictionaries
template <typename T, typename... Args>
std::pair<src_buf_info*, size_type> operator()(Args&&...) requires(std::is_same_v<T, cudf::dictionary32>)
{ CUDF_FAIL("Unsupported type"); }
For T = dictionary32 both are viable; partial ordering prefers the non-variadic primary template over the parameter pack, so the CUDF_FAIL overload is dead code and the dictionary parent is treated as a fixed-width 4-byte column. Its children are never visited by setup_source_buf_info (only string / list / struct recurse), while count_src_bufs and the packed metadata still count the two children, so unpack rebuilds them from mismatched metadata — hence the keys child with the parent's size.
Suggested fix
Make the dictionary overload actually win (e.g. constrain the primary template with requires(!cudf::is_dictionary<T>()), or give the dictionary overload the same non-variadic signature), so that dictionary input fails loudly; or implement proper support by recursing into the indices and keys children as the list/struct overloads do.
Describe the bug
cudf::packfollowed bycudf::unpackon a table containing aDICTIONARY32column does not raise an error, but the unpacked column's keys child has the parent's size (e.g. 5 keys become 100,000), so the column is invalid.contiguous_splithas aCUDF_FAIL("Unsupported type")overload fordictionary32, but it is never selected.Steps/Code to reproduce bug
Output:
Expected behavior
Either a round trip that preserves the dictionary (keys child of size 5), or an explicit
cudf::logic_error("Unsupported type") as the code intends — not a silently malformed column.Environment
main(d45003e0d0):cpp/src/copying/contiguous_split.culines 486-514.Root cause (analysis)
buf_info_functorhas an unconstrained primary templateand a constrained variadic overload meant for dictionaries
For
T = dictionary32both are viable; partial ordering prefers the non-variadic primary template over the parameter pack, so theCUDF_FAILoverload is dead code and the dictionary parent is treated as a fixed-width 4-byte column. Its children are never visited bysetup_source_buf_info(only string / list / struct recurse), whilecount_src_bufsand the packed metadata still count the two children, sounpackrebuilds them from mismatched metadata — hence the keys child with the parent's size.Suggested fix
Make the dictionary overload actually win (e.g. constrain the primary template with
requires(!cudf::is_dictionary<T>()), or give the dictionary overload the same non-variadic signature), so that dictionary input fails loudly; or implement proper support by recursing into the indices and keys children as the list/struct overloads do.