Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add support for different data types in arrays, also on GPU #324

Open
wants to merge 5 commits into
base: main
Choose a base branch
from

Conversation

rhaas80
Copy link
Member

@rhaas80 rhaas80 commented Dec 6, 2024

This adds proper support for data types other than double to CarpetX, also for GPUs.

It introduces a helper class AnyVectorthat can hold any data type (similar to gdata in CarpetLib), as well as a number of switch statements to handle openPMD's typed output routines. 

I am told that very new versions of openPMD-api lets one call the internal un-typed output routine, but at the time this code was written, that was not yet possible, and thus may not be possible on all cluster installed copies of openPMD.

Python code to dump data to compare:

#!/usr/bin/env python3

import openpmd_api as io
import sys
import numpy

series = io.Series(sys.argv[1], io.Access.read_only)

print("Read a Series with openPMD standard version %s" %
      series.openPMD)

print("The Series contains {0} iterations:".format(len(series.iterations)))
for i in series.iterations:
    print("\t {0}".format(i))
print("")

it = 0
i = series.iterations[it]
print("Iteration {0} contains {1} meshes:".format(it, len(i.meshes)))
for m in i.meshes:
    print("\t {0}".format(m))
print("")

gf = i.meshes["testoutput_gf_patch00_lev00"]["testoutput_gf"]
shape = gf.shape
print("Field gf has shape {0} and datatype {1}".format(
      shape, gf.dtype))
arrdata = gf.load_chunk()
series.flush()
numpy.savetxt("testoutput_gf.asc", arrdata.flatten())

for var in ["sc", "a1", "a2", "a3"]:
    for suffix in [""]: #, "_int", "_complex"]:
        data = i.meshes["testoutput_"+var+suffix]["testoutput_"+var+suffix]
        print("Field {0} has shape {1} and datatype {2}".format("testoutput_"+var+suffix, data.shape, data.dtype))
        arrdata = data.load_chunk()
        series.flush()
        numpy.savetxt("testoutput_"+var+suffix+".asc", arrdata.flatten())

@rhaas80
Copy link
Member Author

rhaas80 commented Dec 6, 2024

@rhaas80
Copy link
Member Author

rhaas80 commented Dec 6, 2024

Fixing the various CUDA issues right now.

CarpetX/src/io_openpmd.cxx Outdated Show resolved Hide resolved
@rhaas80 rhaas80 requested a review from eschnett December 6, 2024 21:59
AnyTypeVector(const AnyTypeVector &) = delete;
AnyTypeVector &operator=(const AnyTypeVector &) = delete;
AnyTypeVector &operator=(const AnyTypeVector &&other) {
this->_type = other._type;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is incorrect. The moved-from object will still have its destructor called, which would deallocate the memory. The moved-from object needs to be put into an "inactive" state.

return *this;
}
AnyTypeVector(AnyTypeVector &&other)
: _type(other._type), _data(other._data), _count(other._count) {}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ditto

AnyTypeVector() : _type(-1), _data(nullptr), _count(0){};
AnyTypeVector(int type_, size_t count_)
: _type(type_),
_data(amrex::The_Arena()->alloc(CCTK_VarTypeSize(type_) * count_)),
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks dangerous. Passing an invalid type makes CCTK_VarTypeSize return a negative number, and C++ will convert this into a very large unsigned number for alloc. Better to check and call CCTK_ERROR.


int type() const { return _type; };

void *data_at(size_t i) const {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There should probably be two versions of this function, one const and returning a const void*, and the other non-const and returning a void *.

static_cast<CCTK_COMPLEX *>(cactus_var_ptr), start, count);
break;
default:
assert(0 && "Unexpected variable type");
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd call CCTK_ERROR here because it can't be disabled by compiler options.

std::vector<char> poison(typesize);
// for int: deadbeef for little endian machines
for (size_t i = 0; i < typesize; i += sizeof poison)
std::memcpy(&poison[i], &ipoison, std::min(poison.size(), typesize - i));
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would define three different poison values here (for int, real, and complex). Using just some of the real bits for int might give values that aren't very distinctive. I found that large positive values make for good integer poison. Negative values don't work that well because they are more likely to be clipped off.

start, count);
break;
default:
assert(0 && "Unexpected variable type");
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would call CCTK_ERROR here.

case CCTK_VARIABLE_COMPLEX:
return openPMD::determineDatatype<CCTK_COMPLEX>();
default:
assert(0 && "Unexpected varType");
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CCTK_ERROR

switch (cgroup.vartype) {
case CCTK_VARIABLE_REAL:
record_components.at(vi).storeChunkRaw(
static_cast<CCTK_REAL const *const>(var_ptr), start, count);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be static_cast<CCTK_REAL const *>, the additional const doesn't have any effect since the pointer is an rvalue.

file << sep << arraygroupdata.data.at(tl).at(vi);
switch (cgroup.vartype) {
case CCTK_VARIABLE_REAL:
file << sep << *(CCTK_REAL *)arraygroupdata.data.at(tl).data_at(vi);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be (const CCTK_REAL *)

for (size_t i = 0; i < anytypevector.size(); ++i) {
switch (anytypevector.type()) {
case CCTK_VARIABLE_COMPLEX:
yaml << *(CCTK_COMPLEX *)anytypevector.data_at(i);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be (const CCTK_COMPLEX *).

file << sep << col++ << ":" << varname << ".real";
file << sep << col++ << ":" << varname << ".imag";
} else
assert(0 && "Unexpected variable type");
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Better call CCTK_ERROR

switch (cgroup.vartype) {
case CCTK_VARIABLE_REAL:
file << sep
<< *(CCTK_REAL *)arraygroupdata.data.at(tl).data_at(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(const CCTK_REAL *)

break;
case CCTK_VARIABLE_INT:
file << sep
<< *(CCTK_INT *)arraygroupdata.data.at(tl).data_at(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The expression arraygroupdata.data.at(tl).data_at(np * vi + DI[out_dir] * i) could be calculated before the switch statement to reduce code duplication.

CCTK_REAL poison;
std::memcpy(&poison, &ipoison, sizeof poison);
const size_t typesize = size_t(CCTK_VarTypeSize(group.vartype));
char poison[typesize];
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Didn't you switch to std::vector elsewhere for a similar construct?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, and here it dies not complain about the varuable sized array, but it diez in io_openpmd.cxx. I have no clue why.

I will redo thus all anyway, to have one poision code for bith places where it is used.

CCTK_REAL poison;
std::memcpy(&poison, &ipoison, sizeof poison);
const size_t typesize = size_t(CCTK_VarTypeSize(group.vartype));
char poison[typesize];
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

std::vector?

using std::isnan;
if (CCTK_BUILTIN_EXPECT(nan_handling == nan_handling_t::allow_nans
? std::memcmp(ptr, poison, sizeof *ptr) == 0
: isnan(*ptr),
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You could call isnan(real) || isnan(imag) for complex numbers.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess, yes.

Copy link
Collaborator

@eschnett eschnett left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've left a few comments. Some are important and need to be addressed. Most are suggestions.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants