Skip to content

Commit

Permalink
Merge pull request #24 from peastman/common
Browse files Browse the repository at this point in the history
Converted OpenCL and CUDA to use common platform
  • Loading branch information
peastman authored Aug 12, 2021
2 parents 6f16853 + 4d836c2 commit 608b697
Show file tree
Hide file tree
Showing 19 changed files with 121 additions and 12,731 deletions.
1 change: 1 addition & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ ADD_SUBDIRECTORY(serialization/tests)
# Build the implementations for different platforms

ADD_SUBDIRECTORY(platforms/reference)
ADD_SUBDIRECTORY(platforms/common)

SET(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}")
FIND_PACKAGE(OpenCL QUIET)
Expand Down
24 changes: 15 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
OpenMM Example Plugin
=====================

This project is an example of how to write a plugin for [OpenMM](https://simtk.org/home/openmm).
This project is an example of how to write a plugin for [OpenMM](https://openmm.org).
It includes nearly everything you would want in a real plugin, including implementations for the
Reference, OpenCL, and CUDA platforms, serialization support, test cases, and a Python API. It
is useful as a starting point for anyone who wants to write a plugin.
Expand Down Expand Up @@ -84,6 +84,12 @@ run the test suite.
OpenCL and CUDA Kernels
=======================

The OpenCL and CUDA versions of the force are implemented with the common compute framework.
This allows us to write a single class (`CommonCalcExampleForceKernel`) that provides an
implementation for both platforms at the same time. Device code is written in a subset of
the OpenCL and CUDA languages, with a few macro and function definitions to make them
identical.

The OpenCL and CUDA platforms compile all of their kernels from source at runtime. This
requires you to store all your kernel source in a way that makes it accessible at runtime. That
turns out to be harder than you might think: simply storing source files on disk is brittle,
Expand All @@ -93,13 +99,13 @@ strings in the code, but that is very inconvenient to edit and maintain, especia
doesn't have a clean syntax for multi-line strings.

This project (like OpenMM itself) uses a hybrid mechanism that provides the best of both
approaches. The source code for the OpenCL and CUDA implementations each include a "kernels"
directory. At build time, a CMake script loads every .cl (for OpenCL) or .cu (for CUDA) file
contained in the directory and generates a class with all the file contents as strings. For
example, the OpenCL kernels directory contains a single file called exampleForce.cl. You can
approaches. The source code for the kernels is found in the `platforms/common/src/kernels`
directory. At build time, a CMake script loads every .cc file contained in the directory
and generates a class with all the file contents as strings. For the example plugin, the
directory contains a single file called exampleForce.cc. You can
put anything you want into this file, and then C++ code can access the content of that file
as `OpenCLExampleKernelSources::exampleForce`. If you add more .cl files to this directory,
correspondingly named variables will automatically be added to `OpenCLExampleKernelSources`.
as `CommonExampleKernelSources::exampleForce`. If you add more .cc files to this directory,
correspondingly named variables will automatically be added to `CommonExampleKernelSources`.


Python API
Expand All @@ -111,7 +117,7 @@ It then generates a Python extension module exposing the C++ API in Python.

When building OpenMM's Python API, the interface file is generated automatically from the C++
API. That guarantees the C++ and Python APIs are always synchronized with each other and avoids
the potential bugs that would come from have duplicate definitions. It takes a lot of complex
the potential bugs that would come from having duplicate definitions. It takes a lot of complex
processing to do that, though, and for a single plugin it's far simpler to just write the
interface file by hand. You will find it in the "python" directory.

Expand All @@ -136,7 +142,7 @@ Simbios, the NIH National Center for Physics-Based Simulation of
Biological Structures at Stanford, funded under the NIH Roadmap for
Medical Research, grant U54 GM072970. See https://simtk.org.

Portions copyright (c) 2014 Stanford University and the Authors.
Portions copyright (c) 2014-2021 Stanford University and the Authors.

Authors: Peter Eastman

Expand Down
15 changes: 15 additions & 0 deletions platforms/common/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# Encode the kernel sources into a C++ class.

SET(KERNEL_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/src)
SET(KERNEL_SOURCE_CLASS CommonExampleKernelSources)
SET(KERNELS_CPP ${CMAKE_CURRENT_BINARY_DIR}/src/${KERNEL_SOURCE_CLASS}.cpp)
SET(KERNELS_H ${CMAKE_CURRENT_BINARY_DIR}/src/${KERNEL_SOURCE_CLASS}.h)
INCLUDE_DIRECTORIES(BEFORE ${CMAKE_CURRENT_BINARY_DIR}/src)
FILE(GLOB COMMON_KERNELS ${KERNEL_SOURCE_DIR}/kernels/*.cc)
ADD_CUSTOM_COMMAND(OUTPUT ${KERNELS_CPP} ${KERNELS_H}
COMMAND ${CMAKE_COMMAND}
ARGS -D KERNEL_SOURCE_DIR=${KERNEL_SOURCE_DIR} -D KERNELS_CPP=${KERNELS_CPP} -D KERNELS_H=${KERNELS_H} -D KERNEL_SOURCE_CLASS=${KERNEL_SOURCE_CLASS} -P ${CMAKE_SOURCE_DIR}/platforms/common/EncodeKernelFiles.cmake
DEPENDS ${COMMON_KERNELS}
)
SET_SOURCE_FILES_PROPERTIES(${KERNELS_CPP} ${KERNELS_H} PROPERTIES GENERATED TRUE)
ADD_CUSTOM_TARGET(CommonKernels DEPENDS ${KERNELS_CPP} ${KERNELS_H})
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
FILE(GLOB OPENCL_KERNELS ${CL_SOURCE_DIR}/kernels/*.cl)
SET(CL_FILE_DECLARATIONS)
SET(CL_FILE_DEFINITIONS)
CONFIGURE_FILE(${CL_SOURCE_DIR}/${CL_SOURCE_CLASS}.cpp.in ${CL_KERNELS_CPP})
FOREACH(file ${OPENCL_KERNELS})
FILE(GLOB KERNEL_FILES ${KERNEL_SOURCE_DIR}/kernels/*.cc)
SET(KERNEL_FILE_DECLARATIONS)
CONFIGURE_FILE(${KERNEL_SOURCE_DIR}/${KERNEL_SOURCE_CLASS}.cpp.in ${KERNELS_CPP})
FOREACH(file ${KERNEL_FILES})
# Load the file contents and process it.
FILE(STRINGS ${file} file_content NEWLINE_CONSUME)
# Replace all backslashes by double backslashes as they are being put in a C string.
Expand All @@ -15,13 +14,13 @@ FOREACH(file ${OPENCL_KERNELS})
STRING(REPLACE "\n" "\\n\"\n\"" file_content "${file_content}")

# Determine a name for the variable that will contain this file's contents
FILE(RELATIVE_PATH filename ${CL_SOURCE_DIR}/kernels ${file})
FILE(RELATIVE_PATH filename ${KERNEL_SOURCE_DIR}/kernels ${file})
STRING(LENGTH ${filename} filename_length)
MATH(EXPR filename_length ${filename_length}-3)
STRING(SUBSTRING ${filename} 0 ${filename_length} variable_name)

# Record the variable declaration and definition.
SET(CL_FILE_DECLARATIONS ${CL_FILE_DECLARATIONS}static\ const\ std::string\ ${variable_name};\n)
FILE(APPEND ${CL_KERNELS_CPP} const\ string\ ${CL_SOURCE_CLASS}::${variable_name}\ =\ \"${file_content}\"\;\n)
SET(KERNEL_FILE_DECLARATIONS ${KERNEL_FILE_DECLARATIONS}static\ const\ std::string\ ${variable_name};\n)
FILE(APPEND ${KERNELS_CPP} const\ string\ ${KERNEL_SOURCE_CLASS}::${variable_name}\ =\ \"${file_content}\"\;\n)
ENDFOREACH(file)
CONFIGURE_FILE(${CL_SOURCE_DIR}/${CL_SOURCE_CLASS}.h.in ${CL_KERNELS_H})
CONFIGURE_FILE(${KERNEL_SOURCE_DIR}/${KERNEL_SOURCE_CLASS}.h.in ${KERNELS_H})
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
* USE OR OTHER DEALINGS IN THE SOFTWARE. *
* -------------------------------------------------------------------------- */

#include "CudaExampleKernelSources.h"
#include "CommonExampleKernelSources.h"

using namespace ExamplePlugin;
using namespace std;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#ifndef OPENMM_CUDAEXAMPLEKERNELSOURCES_H_
#define OPENMM_CUDAEXAMPLEKERNELSOURCES_H_
#ifndef OPENMM_COMMONEXAMPLEKERNELSOURCES_H_
#define OPENMM_COMMONEXAMPLEKERNELSOURCES_H_

/* -------------------------------------------------------------------------- *
* OpenMM *
Expand Down Expand Up @@ -37,16 +37,16 @@
namespace ExamplePlugin {

/**
* This class is a central holding place for the source code of CUDA kernels.
* The CMake build script inserts declarations into it based on the .cu files in the
* This class is a central holding place for the source code of common kernels.
* The CMake build script inserts declarations into it based on the .cc files in the
* kernels subfolder.
*/

class CudaExampleKernelSources {
class CommonExampleKernelSources {
public:
@CUDA_FILE_DECLARATIONS@
@KERNEL_FILE_DECLARATIONS@
};

} // namespace ExamplePlugin

#endif /*OPENMM_CUDAEXAMPLEKERNELSOURCES_H_*/
#endif /*OPENMM_COMMONEXAMPLEKERNELSOURCES_H_*/
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
* Biological Structures at Stanford, funded under the NIH Roadmap for *
* Medical Research, grant U54 GM072970. See https://simtk.org. *
* *
* Portions copyright (c) 2014 Stanford University and the Authors. *
* Portions copyright (c) 2014-2021 Stanford University and the Authors. *
* Authors: Peter Eastman *
* Contributors: *
* *
Expand All @@ -29,19 +29,19 @@
* USE OR OTHER DEALINGS IN THE SOFTWARE. *
* -------------------------------------------------------------------------- */

#include "OpenCLExampleKernels.h"
#include "OpenCLExampleKernelSources.h"
#include "CommonExampleKernels.h"
#include "CommonExampleKernelSources.h"
#include "openmm/common/BondedUtilities.h"
#include "openmm/common/ComputeForceInfo.h"
#include "openmm/internal/ContextImpl.h"
#include "openmm/opencl/OpenCLBondedUtilities.h"
#include "openmm/opencl/OpenCLForceInfo.h"

using namespace ExamplePlugin;
using namespace OpenMM;
using namespace std;

class OpenCLExampleForceInfo : public OpenCLForceInfo {
class CommonExampleForceInfo : public ComputeForceInfo {
public:
OpenCLExampleForceInfo(const ExampleForce& force) : OpenCLForceInfo(0), force(force) {
CommonExampleForceInfo(const ExampleForce& force) : force(force) {
}
int getNumParticleGroups() {
return force.getNumBonds();
Expand All @@ -65,41 +65,36 @@ class OpenCLExampleForceInfo : public OpenCLForceInfo {
const ExampleForce& force;
};

OpenCLCalcExampleForceKernel::~OpenCLCalcExampleForceKernel() {
if (params != NULL)
delete params;
}

void OpenCLCalcExampleForceKernel::initialize(const System& system, const ExampleForce& force) {
int numContexts = cl.getPlatformData().contexts.size();
int startIndex = cl.getContextIndex()*force.getNumBonds()/numContexts;
int endIndex = (cl.getContextIndex()+1)*force.getNumBonds()/numContexts;
void CommonCalcExampleForceKernel::initialize(const System& system, const ExampleForce& force) {
int numContexts = cc.getNumContexts();
int startIndex = cc.getContextIndex()*force.getNumBonds()/numContexts;
int endIndex = (cc.getContextIndex()+1)*force.getNumBonds()/numContexts;
numBonds = endIndex-startIndex;
if (numBonds == 0)
return;
vector<vector<int> > atoms(numBonds, vector<int>(2));
params = OpenCLArray::create<mm_float2>(cl, numBonds, "bondParams");
params.initialize<mm_float2>(cc, numBonds, "bondParams");
vector<mm_float2> paramVector(numBonds);
for (int i = 0; i < numBonds; i++) {
double length, k;
force.getBondParameters(startIndex+i, atoms[i][0], atoms[i][1], length, k);
paramVector[i] = mm_float2((cl_float) length, (cl_float) k);
paramVector[i] = mm_float2((float) length, (float) k);
}
params->upload(paramVector);
params.upload(paramVector);
map<string, string> replacements;
replacements["PARAMS"] = cl.getBondedUtilities().addArgument(params->getDeviceBuffer(), "float2");
cl.getBondedUtilities().addInteraction(atoms, cl.replaceStrings(OpenCLExampleKernelSources::exampleForce, replacements), force.getForceGroup());
cl.addForce(new OpenCLExampleForceInfo(force));
replacements["PARAMS"] = cc.getBondedUtilities().addArgument(params, "float2");
cc.getBondedUtilities().addInteraction(atoms, cc.replaceStrings(CommonExampleKernelSources::exampleForce, replacements), force.getForceGroup());
cc.addForce(new CommonExampleForceInfo(force));
}

double OpenCLCalcExampleForceKernel::execute(ContextImpl& context, bool includeForces, bool includeEnergy) {
double CommonCalcExampleForceKernel::execute(ContextImpl& context, bool includeForces, bool includeEnergy) {
return 0.0;
}

void OpenCLCalcExampleForceKernel::copyParametersToContext(ContextImpl& context, const ExampleForce& force) {
int numContexts = cl.getPlatformData().contexts.size();
int startIndex = cl.getContextIndex()*force.getNumBonds()/numContexts;
int endIndex = (cl.getContextIndex()+1)*force.getNumBonds()/numContexts;
void CommonCalcExampleForceKernel::copyParametersToContext(ContextImpl& context, const ExampleForce& force) {
int numContexts = cc.getNumContexts();
int startIndex = cc.getContextIndex()*force.getNumBonds()/numContexts;
int endIndex = (cc.getContextIndex()+1)*force.getNumBonds()/numContexts;
if (numBonds != endIndex-startIndex)
throw OpenMMException("updateParametersInContext: The number of bonds has changed");
if (numBonds == 0)
Expand All @@ -112,12 +107,12 @@ void OpenCLCalcExampleForceKernel::copyParametersToContext(ContextImpl& context,
int atom1, atom2;
double length, k;
force.getBondParameters(startIndex+i, atom1, atom2, length, k);
paramVector[i] = mm_float2((cl_float) length, (cl_float) k);
paramVector[i] = mm_float2((float) length, (float) k);
}
params->upload(paramVector);
params.upload(paramVector);

// Mark that the current reordering may be invalid.

cl.invalidateMolecules();
cc.invalidateMolecules();
}

Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#ifndef CUDA_EXAMPLE_KERNELS_H_
#define CUDA_EXAMPLE_KERNELS_H_
#ifndef COMMON_EXAMPLE_KERNELS_H_
#define COMMON_EXAMPLE_KERNELS_H_

/* -------------------------------------------------------------------------- *
* OpenMM *
Expand All @@ -9,7 +9,7 @@
* Biological Structures at Stanford, funded under the NIH Roadmap for *
* Medical Research, grant U54 GM072970. See https://simtk.org. *
* *
* Portions copyright (c) 2014 Stanford University and the Authors. *
* Portions copyright (c) 2014-2021 Stanford University and the Authors. *
* Authors: Peter Eastman *
* Contributors: *
* *
Expand All @@ -33,20 +33,19 @@
* -------------------------------------------------------------------------- */

#include "ExampleKernels.h"
#include "openmm/cuda/CudaContext.h"
#include "openmm/cuda/CudaArray.h"
#include "openmm/common/ComputeContext.h"
#include "openmm/common/ComputeArray.h"

namespace ExamplePlugin {

/**
* This kernel is invoked by ExampleForce to calculate the forces acting on the system and the energy of the system.
*/
class CudaCalcExampleForceKernel : public CalcExampleForceKernel {
class CommonCalcExampleForceKernel : public CalcExampleForceKernel {
public:
CudaCalcExampleForceKernel(std::string name, const OpenMM::Platform& platform, OpenMM::CudaContext& cu, const OpenMM::System& system) :
CalcExampleForceKernel(name, platform), hasInitializedKernel(false), cu(cu), system(system), params(NULL) {
CommonCalcExampleForceKernel(std::string name, const OpenMM::Platform& platform, OpenMM::ComputeContext& cc, const OpenMM::System& system) :
CalcExampleForceKernel(name, platform), hasInitializedKernel(false), cc(cc), system(system) {
}
~CudaCalcExampleForceKernel();
/**
* Initialize the kernel.
*
Expand All @@ -73,11 +72,11 @@ class CudaCalcExampleForceKernel : public CalcExampleForceKernel {
private:
int numBonds;
bool hasInitializedKernel;
OpenMM::CudaContext& cu;
OpenMM::ComputeContext& cc;
const OpenMM::System& system;
OpenMM::CudaArray* params;
OpenMM::ComputeArray params;
};

} // namespace ExamplePlugin

#endif /*CUDA_EXAMPLE_KERNELS_H_*/
#endif /*COMMON_EXAMPLE_KERNELS_H_*/
File renamed without changes.
38 changes: 16 additions & 22 deletions platforms/cuda/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ SET(EXAMPLE_CUDA_LIBRARY_NAME ExamplePluginCUDA)

SET(SHARED_TARGET ${EXAMPLE_CUDA_LIBRARY_NAME})


# These are all the places to search for header files which are
# to be part of the API.
SET(API_INCLUDE_DIRS "${CMAKE_CURRENT_SOURCE_DIR}/include" "${CMAKE_CURRENT_SOURCE_DIR}/include/internal")
Expand All @@ -25,38 +24,33 @@ ENDFOREACH(dir)
SET(SOURCE_FILES) # empty
SET(SOURCE_INCLUDE_FILES)

FILE(GLOB_RECURSE src_files ${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp ${CMAKE_CURRENT_SOURCE_DIR}/${subdir}/src/*.c)
FILE(GLOB incl_files ${CMAKE_CURRENT_SOURCE_DIR}/src/*.h)
SET(SOURCE_FILES ${SOURCE_FILES} ${src_files}) #append
SET(SOURCE_INCLUDE_FILES ${SOURCE_INCLUDE_FILES} ${incl_files})
INCLUDE_DIRECTORIES(BEFORE ${CMAKE_CURRENT_SOURCE_DIR}/include)
SET(OPENMM_SOURCE_SUBDIRS . ../common)
FOREACH(subdir ${OPENMM_SOURCE_SUBDIRS})
FILE(GLOB_RECURSE src_files ${CMAKE_CURRENT_SOURCE_DIR}/${subdir}/src/*.cpp ${CMAKE_CURRENT_SOURCE_DIR}/${subdir}/${subdir}/src/*.c)
FILE(GLOB incl_files ${CMAKE_CURRENT_SOURCE_DIR}/${subdir}/src/*.h)
SET(SOURCE_FILES ${SOURCE_FILES} ${src_files}) #append
SET(SOURCE_INCLUDE_FILES ${SOURCE_INCLUDE_FILES} ${incl_files})
ENDFOREACH(subdir)

SET(COMMON_KERNELS_CPP ${CMAKE_CURRENT_BINARY_DIR}/../common/src/CommonExampleKernelSources.cpp)
SET(SOURCE_FILES ${SOURCE_FILES} ${COMMON_KERNELS_CPP})

INCLUDE_DIRECTORIES(BEFORE ${CMAKE_CURRENT_SOURCE_DIR}/src)
INCLUDE_DIRECTORIES(BEFORE ${CMAKE_CURRENT_SOURCE_DIR}/../common/src)
INCLUDE_DIRECTORIES(BEFORE ${CMAKE_SOURCE_DIR}/platforms/cuda/include)
INCLUDE_DIRECTORIES(BEFORE ${CMAKE_SOURCE_DIR}/platforms/cuda/src)
INCLUDE_DIRECTORIES(BEFORE ${CMAKE_BINARY_DIR}/platforms/cuda/src)

# Set variables needed for encoding kernel sources into a C++ class

SET(CUDA_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/src)
SET(CUDA_SOURCE_CLASS CudaExampleKernelSources)
SET(CUDA_KERNELS_CPP ${CMAKE_CURRENT_BINARY_DIR}/src/${CUDA_SOURCE_CLASS}.cpp)
SET(CUDA_KERNELS_H ${CMAKE_CURRENT_BINARY_DIR}/src/${CUDA_SOURCE_CLASS}.h)
SET(SOURCE_FILES ${SOURCE_FILES} ${CUDA_KERNELS_CPP} ${CUDA_KERNELS_H})
INCLUDE_DIRECTORIES(BEFORE ${CMAKE_CURRENT_BINARY_DIR}/src)
INCLUDE_DIRECTORIES(BEFORE ${CMAKE_SOURCE_DIR}/platforms/common/include)
INCLUDE_DIRECTORIES(BEFORE ${CMAKE_BINARY_DIR}/platforms/common/src)
INCLUDE_DIRECTORIES(BEFORE ${CMAKE_CURRENT_BINARY_DIR}/../common/src)

# Create the library

INCLUDE_DIRECTORIES(${CUDA_TOOLKIT_INCLUDE})

FILE(GLOB CUDA_KERNELS ${CUDA_SOURCE_DIR}/kernels/*.cu)
ADD_CUSTOM_COMMAND(OUTPUT ${CUDA_KERNELS_CPP} ${CUDA_KERNELS_H}
COMMAND ${CMAKE_COMMAND}
ARGS -D CUDA_SOURCE_DIR=${CUDA_SOURCE_DIR} -D CUDA_KERNELS_CPP=${CUDA_KERNELS_CPP} -D CUDA_KERNELS_H=${CUDA_KERNELS_H} -D CUDA_SOURCE_CLASS=${CUDA_SOURCE_CLASS} -P ${CMAKE_SOURCE_DIR}/platforms/cuda/EncodeCUDAFiles.cmake
DEPENDS ${CUDA_KERNELS}
)
SET_SOURCE_FILES_PROPERTIES(${CUDA_KERNELS_CPP} ${CUDA_KERNELS_H} PROPERTIES GENERATED TRUE)
SET_SOURCE_FILES_PROPERTIES(${COMMON_KERNELS_CPP} PROPERTIES GENERATED TRUE)
ADD_LIBRARY(${SHARED_TARGET} SHARED ${SOURCE_FILES} ${SOURCE_INCLUDE_FILES} ${API_INCLUDE_FILES})
ADD_DEPENDENCIES(${SHARED_TARGET} CommonKernels)

TARGET_LINK_LIBRARIES(${SHARED_TARGET} ${CUDA_LIBRARIES})
TARGET_LINK_LIBRARIES(${SHARED_TARGET} OpenMM)
Expand Down
Loading

0 comments on commit 608b697

Please sign in to comment.