Skip to content
Draft
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
api: reject CSC in host() clearly and cover every storage in the test
CSC to dense is not implemented, so host() on a CSC array now fails with AF_ERR_NOT_SUPPORTED and a message instead of an argument error from deep inside the conversion. The docs say CSR and COO. The test gains an fp64 guard and a case that compares host() against the dense source for CSR and COO and expects the CSC rejection.
  • Loading branch information
melonakos committed Sep 10, 2026
commit 1f7efa94da1a3a4a39731e8375a917ebd518ecf8
17 changes: 10 additions & 7 deletions include/af/array.h
Original file line number Diff line number Diff line change
Expand Up @@ -656,16 +656,18 @@ namespace af
/**
Copy array data to host and return host pointer

For a sparse array the dense representation is copied, so the
returned buffer holds dims().elements() values.
For a CSR or COO sparse array the dense representation is copied,
so the returned buffer holds dims().elements() values. CSC sparse
arrays are not supported.
*/
template<typename T> T* host() const;

/**
Copy array data to existing host pointer

For a sparse array the dense representation is copied, so \p ptr
must hold dims().elements() values.
For a CSR or COO sparse array the dense representation is copied,
so \p ptr must hold dims().elements() values. CSC sparse arrays
are not supported.
*/
void host(void *ptr) const;

Expand Down Expand Up @@ -1690,9 +1692,10 @@ extern "C" {
/**
Copy data from an af_array to a C pointer.

Needs to used in conjunction with the two functions above. For a
sparse array the dense representation is copied, so \p data must hold
af_get_elements() values.
Needs to used in conjunction with the two functions above. For a CSR
or COO sparse array the dense representation is copied, so \p data
must hold af_get_elements() values. CSC sparse arrays are not
supported.
*/
AFAPI af_err af_get_data_ptr(void *data, const af_array arr);

Expand Down
8 changes: 7 additions & 1 deletion src/api/c/array.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,13 @@ af_err af_get_data_ptr(void *data, const af_array arr) {
// back the dense data rather than rejecting the array.
ReleaseOnExit dense{nullptr};
if (info.isSparse()) {
AF_CHECK(af_sparse_convert_to(&dense.arr, arr, AF_STORAGE_DENSE));
if (getSparseArrayBase(arr).getStorage() == AF_STORAGE_CSC) {
AF_ERROR(
"Copying a CSC sparse array to the host is not supported; "
"convert it to CSR or COO first",
AF_ERR_NOT_SUPPORTED);
}
AF_CHECK(af_sparse_to_dense(&dense.arr, arr));
}
const af_array src = dense.arr ? dense.arr : arr;

Expand Down
36 changes: 35 additions & 1 deletion test/sparse.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -298,6 +298,7 @@ TYPED_TEST(Sparse, EmptyDeepCopy) {
}

TEST(Sparse, HostOfSparseReturnsDense_ISSUE_3703) {
SUPPORTED_TYPE_CHECK(double);
const int rows = 8, cols = 4;
array values = af::constant(1.0, rows, f64);
array row_ptr = af::iota(dim4(rows + 1), dim4(1), s32);
Expand All @@ -312,14 +313,47 @@ TEST(Sparse, HostOfSparseReturnsDense_ISSUE_3703) {

double *h = sp.host<double>();
ASSERT_NE(h, nullptr);
for (int i = 0; i < rows * cols; i++) { ASSERT_EQ(gold[i], h[i]) << "at " << i; }
for (int i = 0; i < rows * cols; i++) {
ASSERT_EQ(gold[i], h[i]) << "at " << i;
}
af::freeHost(h);

vector<double> into(rows * cols, -1.0);
sp.host(into.data());
ASSERT_EQ(gold, into);
}

TEST(Sparse, HostOfSparseMatchesDenseForEveryStorage_ISSUE_3703) {
const int rows = 6, cols = 5;
array dense = randu(rows, cols, f32);
// Sparsify roughly half of it
dense(dense < 0.5f) = 0.f;
vector<float> gold(rows * cols);
dense.host(gold.data());

const af_storage storages[] = {AF_STORAGE_CSR, AF_STORAGE_COO};
for (af_storage storage : storages) {
array sp = af::sparse(dense, storage);
ASSERT_TRUE(sp.issparse());
vector<float> got(rows * cols, -1.f);
sp.host(got.data());
ASSERT_EQ(gold, got) << "storage " << static_cast<int>(storage);
}

// CSC cannot be created from dense; the CSC of A is the CSR of A^T with
// the index arrays reinterpreted. CSC to dense is not implemented, so
// host() must reject it clearly rather than copy garbage.
array csrT = af::sparse(dense.T(), AF_STORAGE_CSR);
// For CSC the rowIdx argument holds the nnz row indices and the colIdx
// argument holds the cols + 1 column pointers
array csc = af::sparse(rows, cols, sparseGetValues(csrT),
sparseGetColIdx(csrT), sparseGetRowIdx(csrT),
AF_STORAGE_CSC);
ASSERT_TRUE(csc.issparse());
vector<float> gotCsc(rows * cols, -1.f);
ASSERT_THROW(csc.host(gotCsc.data()), af::exception);
}

TEST(Sparse, CPPSparseFromHostArrays) {
//! [ex_sparse_host_arrays]

Expand Down
Loading