Repository: systemml Updated Branches: refs/heads/master 7dc61c05b -> fe9b023c7
[SYSTEMML-1630] Allow setting and resetting of sysml.native.blas property for different execution Project: http://git-wip-us.apache.org/repos/asf/systemml/repo Commit: http://git-wip-us.apache.org/repos/asf/systemml/commit/fe9b023c Tree: http://git-wip-us.apache.org/repos/asf/systemml/tree/fe9b023c Diff: http://git-wip-us.apache.org/repos/asf/systemml/diff/fe9b023c Branch: refs/heads/master Commit: fe9b023c750939f5a3e8e74cbc8202aca9b14fe9 Parents: 7dc61c0 Author: Niketan Pansare <[email protected]> Authored: Thu Nov 16 14:12:29 2017 -0800 Committer: Niketan Pansare <[email protected]> Committed: Thu Nov 16 14:12:29 2017 -0800 ---------------------------------------------------------------------- .../apache/sysml/api/ScriptExecutorUtils.java | 8 +- .../org/apache/sysml/utils/NativeHelper.java | 306 +++++++++++-------- .../java/org/apache/sysml/utils/Statistics.java | 4 +- 3 files changed, 187 insertions(+), 131 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/systemml/blob/fe9b023c/src/main/java/org/apache/sysml/api/ScriptExecutorUtils.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/sysml/api/ScriptExecutorUtils.java b/src/main/java/org/apache/sysml/api/ScriptExecutorUtils.java index d69a863..253a317 100644 --- a/src/main/java/org/apache/sysml/api/ScriptExecutorUtils.java +++ b/src/main/java/org/apache/sysml/api/ScriptExecutorUtils.java @@ -77,12 +77,8 @@ public class ScriptExecutorUtils { DMLScript.FINEGRAINED_STATISTICS = DMLScript.STATISTICS && dmlconf.getBooleanValue(DMLConfig.EXTRA_FINEGRAINED_STATS); DMLScript.SYNCHRONIZE_GPU = dmlconf.getBooleanValue(DMLConfig.SYNCHRONIZE_GPU); DMLScript.EAGER_CUDA_FREE = dmlconf.getBooleanValue(DMLConfig.EAGER_CUDA_FREE); - DMLScript.STATISTICS_MAX_WRAP_LEN = dmlconf.getIntValue(DMLConfig.STATS_MAX_WRAP_LEN); - - String customLibPath = dmlconf.getTextValue(DMLConfig.NATIVE_BLAS_DIR); - if(!customLibPath.equalsIgnoreCase("none")) { - NativeHelper.initializeCustomBLAS(customLibPath, dmlconf.getTextValue(DMLConfig.NATIVE_BLAS)); - } + DMLScript.STATISTICS_MAX_WRAP_LEN = dmlconf.getIntValue(DMLConfig.STATS_MAX_WRAP_LEN); + NativeHelper.initialize(dmlconf.getTextValue(DMLConfig.NATIVE_BLAS_DIR), dmlconf.getTextValue(DMLConfig.NATIVE_BLAS).trim()); if(DMLScript.USE_ACCELERATOR) { DMLScript.FLOATING_POINT_PRECISION = dmlconf.getTextValue(DMLConfig.FLOATING_POINT_PRECISION); http://git-wip-us.apache.org/repos/asf/systemml/blob/fe9b023c/src/main/java/org/apache/sysml/utils/NativeHelper.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/sysml/utils/NativeHelper.java b/src/main/java/org/apache/sysml/utils/NativeHelper.java index c9c2e08..db8e74b 100644 --- a/src/main/java/org/apache/sysml/utils/NativeHelper.java +++ b/src/main/java/org/apache/sysml/utils/NativeHelper.java @@ -24,7 +24,6 @@ import java.io.IOException; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import java.util.HashMap; import java.util.Vector; import java.io.InputStream; import java.io.OutputStream; @@ -43,141 +42,207 @@ import org.apache.sysml.runtime.DMLRuntimeException; * By default, it first tries to load Intel MKL, else tries to load OpenBLAS. */ public class NativeHelper { - private static boolean isSystemMLLoaded = false; + + public static enum NativeBlasState { + NOT_ATTEMPTED_LOADING_NATIVE_BLAS, + SUCCESSFULLY_LOADED_NATIVE_BLAS_AND_IN_USE, + SUCCESSFULLY_LOADED_NATIVE_BLAS_AND_NOT_IN_USE, + ATTEMPTED_LOADING_NATIVE_BLAS_UNSUCCESSFULLY + }; + + public static NativeBlasState CURRENT_NATIVE_BLAS_STATE = NativeBlasState.NOT_ATTEMPTED_LOADING_NATIVE_BLAS; + private static String blasType; private static final Log LOG = LogFactory.getLog(NativeHelper.class.getName()); - private static HashMap<String, String> supportedArchitectures = new HashMap<>(); - public static String blasType; + + // Useful for deciding whether to use native BLAS in parfor environment. private static int maxNumThreads = -1; private static boolean setMaxNumThreads = false; - static { - // Note: we only support 64 bit Java on x86 and AMD machine - supportedArchitectures.put("x86_64", "x86_64"); - supportedArchitectures.put("amd64", "x86_64"); + + /** + * Called by Statistics to print the loaded BLAS. + * + * @return empty string or the BLAS that is loaded + */ + public static String getCurrentBLAS() { + return blasType != null ? blasType : ""; } - - private static boolean attemptedLoading = false; - - private static String hintOnFailures = ""; - - public static void initializeCustomBLAS(String customLibPath, String userSpecifiedBLAS) throws DMLRuntimeException { - if(attemptedLoading && blasType != null && isSupportedBLAS(userSpecifiedBLAS) && !blasType.equalsIgnoreCase(userSpecifiedBLAS) ) { + + /** + * Called by runtime to check if the BLAS is available for exploitation + * + * @return true if CURRENT_NATIVE_BLAS_STATE is SUCCESSFULLY_LOADED_NATIVE_BLAS_AND_NOT_IN_USE else false + */ + public static boolean isNativeLibraryLoaded() { + if(!isBLASLoaded()) { + DMLConfig dmlConfig = ConfigurationManager.getDMLConfig(); + String userSpecifiedBLAS = (dmlConfig == null) ? "auto" : dmlConfig.getTextValue(DMLConfig.NATIVE_BLAS).trim().toLowerCase(); + String customLibPath = (dmlConfig == null) ? "none" : dmlConfig.getTextValue(DMLConfig.NATIVE_BLAS_DIR).trim().toLowerCase(); + performLoading(customLibPath, userSpecifiedBLAS); + } + if(maxNumThreads == -1) + maxNumThreads = OptimizerUtils.getConstrainedNumThreads(-1); + if(CURRENT_NATIVE_BLAS_STATE == NativeBlasState.SUCCESSFULLY_LOADED_NATIVE_BLAS_AND_IN_USE && !setMaxNumThreads && maxNumThreads != -1) { + // This method helps us decide whether to use GetPrimitiveArrayCritical or GetDoubleArrayElements in JNI as each has different tradeoffs. + // In current implementation, we always use GetPrimitiveArrayCritical as it has proven to be fastest. + // We can revisit this decision later and hence I would not recommend removing this method. + setMaxNumThreads(maxNumThreads); + setMaxNumThreads = true; + } + return CURRENT_NATIVE_BLAS_STATE == NativeBlasState.SUCCESSFULLY_LOADED_NATIVE_BLAS_AND_IN_USE; + } + + /** + * Initialize the native library before executing the DML program + * + * @param customLibPath specified by sysml.native.blas.directory + * @param userSpecifiedBLAS specified by sysml.native.blas + * @throws DMLRuntimeException if error + */ + public static void initialize(String customLibPath, String userSpecifiedBLAS) throws DMLRuntimeException { + if(isBLASLoaded() && isSupportedBLAS(userSpecifiedBLAS) && !blasType.equalsIgnoreCase(userSpecifiedBLAS)) { throw new DMLRuntimeException("Cannot replace previously loaded blas \"" + blasType + "\" with \"" + userSpecifiedBLAS + "\"."); } - else { - init(customLibPath, userSpecifiedBLAS); + else if(isBLASLoaded() && userSpecifiedBLAS.equalsIgnoreCase("none")) { + CURRENT_NATIVE_BLAS_STATE = NativeBlasState.SUCCESSFULLY_LOADED_NATIVE_BLAS_AND_NOT_IN_USE; + } + else if(isBLASLoaded() && userSpecifiedBLAS.equalsIgnoreCase(blasType)) { + CURRENT_NATIVE_BLAS_STATE = NativeBlasState.SUCCESSFULLY_LOADED_NATIVE_BLAS_AND_IN_USE; + } + else if(!isBLASLoaded() && isSupportedBLAS(userSpecifiedBLAS)) { + performLoading(customLibPath, userSpecifiedBLAS); } } + /** + * Return true if the given BLAS type is supported. + * + * @param userSpecifiedBLAS BLAS type specified via sysml.native.blas property + * @return true if the userSpecifiedBLAS is auto | mkl | openblas, else false + */ private static boolean isSupportedBLAS(String userSpecifiedBLAS) { - return userSpecifiedBLAS.equalsIgnoreCase("auto") || userSpecifiedBLAS.equalsIgnoreCase("mkl") || userSpecifiedBLAS.equalsIgnoreCase("openblas"); + return userSpecifiedBLAS.equalsIgnoreCase("auto") || + userSpecifiedBLAS.equalsIgnoreCase("mkl") || + userSpecifiedBLAS.equalsIgnoreCase("openblas"); + } + + /** + * Note: we only support 64 bit Java on x86 and AMD machine + * + * @return true if the hardware architecture is supported + */ + private static boolean isSupportedArchitecture() { + if(SystemUtils.OS_ARCH.equals("x86_64") || SystemUtils.OS_ARCH.equals("amd64")) { + return true; + } + LOG.info("Unsupported architecture for native BLAS:" + SystemUtils.OS_ARCH); + return false; + } + + /** + * Check if native BLAS libraries have been successfully loaded + * @return true if CURRENT_NATIVE_BLAS_STATE is SUCCESSFULLY_LOADED_NATIVE_BLAS_AND_IN_USE or SUCCESSFULLY_LOADED_NATIVE_BLAS_AND_NOT_IN_USE + */ + private static boolean isBLASLoaded() { + return CURRENT_NATIVE_BLAS_STATE == NativeBlasState.SUCCESSFULLY_LOADED_NATIVE_BLAS_AND_IN_USE || + CURRENT_NATIVE_BLAS_STATE == NativeBlasState.SUCCESSFULLY_LOADED_NATIVE_BLAS_AND_NOT_IN_USE; + } + + /** + * Check if we should attempt to perform loading. + * If custom library path is provided, we should attempt to load again if not already loaded. + * + * @param customLibPath custom library path + * @return true if we should attempt to load blas again + */ + private static boolean shouldReload(String customLibPath) { + boolean isValidBLASDirectory = customLibPath != null && !customLibPath.equalsIgnoreCase("none"); + return CURRENT_NATIVE_BLAS_STATE == NativeBlasState.NOT_ATTEMPTED_LOADING_NATIVE_BLAS || + (isValidBLASDirectory && !isBLASLoaded()); } // Performing loading in a method instead of a static block will throw a detailed stack trace in case of fatal errors - private static void init(String customLibPath, String userSpecifiedBLAS) { + private static void performLoading(String customLibPath, String userSpecifiedBLAS) { // Only Linux supported for BLAS if(!SystemUtils.IS_OS_LINUX) return; // attemptedLoading variable ensures that we don't try to load SystemML and other dependencies // again and again especially in the parfor (hence the double-checking with synchronized). - if(!attemptedLoading || customLibPath != null) { - // ------------------------------------------------------------------------------------- - if(userSpecifiedBLAS == null) { - DMLConfig dmlConfig = ConfigurationManager.getDMLConfig(); - userSpecifiedBLAS = (dmlConfig == null) ? "auto" : dmlConfig.getTextValue(DMLConfig.NATIVE_BLAS).trim().toLowerCase(); - } - if(isSupportedBLAS(userSpecifiedBLAS)) { - long start = System.nanoTime(); - if(!supportedArchitectures.containsKey(SystemUtils.OS_ARCH)) { - LOG.info("Unsupported architecture for native BLAS:" + SystemUtils.OS_ARCH); - return; - } - synchronized(NativeHelper.class) { - if(!attemptedLoading || customLibPath != null) { - // ----------------------------------------------------------------------------- - // ============================================================================= - // By default, we will native.blas=true and we will attempt to load MKL first. - // If MKL is not enabled then we try to load OpenBLAS. - // If both MKL and OpenBLAS are not available we fall back to Java BLAS. - if(userSpecifiedBLAS.equalsIgnoreCase("auto")) { - blasType = isMKLAvailable(customLibPath) ? "mkl" : isOpenBLASAvailable(customLibPath) ? "openblas" : null; - if(blasType == null) - LOG.info("Unable to load either MKL or OpenBLAS due to " + hintOnFailures); - } - else if(userSpecifiedBLAS.equalsIgnoreCase("mkl")) { - blasType = isMKLAvailable(customLibPath) ? "mkl" : null; - if(blasType == null) - LOG.info("Unable to load MKL due to " + hintOnFailures); - } - else if(userSpecifiedBLAS.equalsIgnoreCase("openblas")) { - blasType = isOpenBLASAvailable(customLibPath) ? "openblas" : null; - if(blasType == null) - LOG.info("Unable to load OpenBLAS due to " + hintOnFailures); - } - else { - // Only thrown at development time. - throw new RuntimeException("Unsupported BLAS:" + userSpecifiedBLAS); - } - // ============================================================================= - if(blasType != null && loadLibraryHelper("libsystemml_" + blasType + "-Linux-x86_64.so")) { - String blasPathAndHint = ""; - // ------------------------------------------------------------ - // This logic gets the list of native libraries that are loaded - if(LOG.isDebugEnabled()) { - // Only perform the checking of library paths when DEBUG is enabled to avoid runtime overhead. - try { - java.lang.reflect.Field loadedLibraryNamesField = ClassLoader.class.getDeclaredField("loadedLibraryNames"); - loadedLibraryNamesField.setAccessible(true); - @SuppressWarnings("unchecked") - Vector<String> libraries = (Vector<String>) loadedLibraryNamesField.get(ClassLoader.getSystemClassLoader()); - LOG.debug("List of native libraries loaded:" + libraries); - for(String library : libraries) { - if(library.contains("libmkl_rt") || library.contains("libopenblas")) { - blasPathAndHint = " from the path " + library; - break; - } - } - } catch (NoSuchFieldException | SecurityException | IllegalArgumentException | IllegalAccessException e) { - LOG.debug("Error while finding list of native libraries:" + e.getMessage()); - } - } - // ------------------------------------------------------------ - - LOG.info("Using native blas: " + blasType + blasPathAndHint); - isSystemMLLoaded = true; - } + if(shouldReload(customLibPath) && isSupportedBLAS(userSpecifiedBLAS) && isSupportedArchitecture()) { + long start = System.nanoTime(); + synchronized(NativeHelper.class) { + if(shouldReload(customLibPath)) { + // Set attempted loading unsuccessful in case of exception + CURRENT_NATIVE_BLAS_STATE = NativeBlasState.ATTEMPTED_LOADING_NATIVE_BLAS_UNSUCCESSFULLY; + String [] blas = new String[] { userSpecifiedBLAS }; + if(userSpecifiedBLAS.equalsIgnoreCase("auto")) { + blas = new String[] { "mkl", "openblas" }; + } + if(checkAndLoadBLAS(customLibPath, blas) && loadLibraryHelper("libsystemml_" + blasType + "-Linux-x86_64.so")) { + LOG.info("Using native blas: " + blasType + getNativeBLASPath()); + CURRENT_NATIVE_BLAS_STATE = NativeBlasState.SUCCESSFULLY_LOADED_NATIVE_BLAS_AND_IN_USE; } } - double timeToLoadInMilliseconds = (System.nanoTime()-start)*1e-6; - if(timeToLoadInMilliseconds > 1000) - LOG.warn("Time to load native blas: " + timeToLoadInMilliseconds + " milliseconds."); } - else { - LOG.debug("Using internal Java BLAS as native BLAS support the configuration 'sysml.native.blas'=" + userSpecifiedBLAS + "."); + double timeToLoadInMilliseconds = (System.nanoTime()-start)*1e-6; + if(timeToLoadInMilliseconds > 1000) + LOG.warn("Time to load native blas: " + timeToLoadInMilliseconds + " milliseconds."); + } + else if(LOG.isDebugEnabled() && !isSupportedBLAS(userSpecifiedBLAS)) { + LOG.debug("Using internal Java BLAS as native BLAS support the configuration 'sysml.native.blas'=" + userSpecifiedBLAS + "."); + } + } + + private static boolean checkAndLoadBLAS(String customLibPath, String [] listBLAS) { + if(customLibPath != null && customLibPath.equalsIgnoreCase("none")) + customLibPath = null; + + boolean isLoaded = false; + for(int i = 0; i < listBLAS.length; i++) { + String blas = listBLAS[i]; + if(blas.equalsIgnoreCase("mkl")) { + isLoaded = loadBLAS(customLibPath, "mkl_rt", null); + } + else if(blas.equalsIgnoreCase("openblas")) { + boolean isGompLoaded = loadBLAS(customLibPath, "gomp", "gomp required for loading OpenBLAS-enabled SystemML library"); + if(isGompLoaded) { + isLoaded = loadBLAS(customLibPath, "openblas", null); + } + } + if(isLoaded) { + blasType = blas; + break; } - attemptedLoading = true; } + return isLoaded; } - - public static boolean isNativeLibraryLoaded() { - // We allow BLAS to be enabled or disabled or explicitly selected in one of the two ways: - // 1. DML Configuration: native.blas (boolean flag) - // 2. Environment variable: SYSTEMML_BLAS (can be set to mkl, openblas or none) - // The option 1 will be removed in later SystemML versions. - // The option 2 is useful for two reasons: - // - Developer testing of different BLAS - // - Provides fine-grained control. Certain machines could use mkl while others use openblas, etc. - init(null, null); - if(maxNumThreads == -1) - maxNumThreads = OptimizerUtils.getConstrainedNumThreads(-1); - if(isSystemMLLoaded && !setMaxNumThreads && maxNumThreads != -1) { - // This method helps us decide whether to use GetPrimitiveArrayCritical or GetDoubleArrayElements in JNI as each has different tradeoffs. - // In current implementation, we always use GetPrimitiveArrayCritical as it has proven to be fastest. - // We can revisit this decision later and hence I would not recommend removing this method. - setMaxNumThreads(maxNumThreads); - setMaxNumThreads = true; + + /** + * Useful method for debugging. + * + * @return empty string (if !LOG.isDebugEnabled()) or the path from where openblas or mkl is loaded. + */ + private static String getNativeBLASPath() { + String blasPathAndHint = ""; + if(LOG.isDebugEnabled()) { + // Only perform the checking of library paths when DEBUG is enabled to avoid runtime overhead. + try { + java.lang.reflect.Field loadedLibraryNamesField = ClassLoader.class.getDeclaredField("loadedLibraryNames"); + loadedLibraryNamesField.setAccessible(true); + @SuppressWarnings("unchecked") + Vector<String> libraries = (Vector<String>) loadedLibraryNamesField.get(ClassLoader.getSystemClassLoader()); + LOG.debug("List of native libraries loaded:" + libraries); + for(String library : libraries) { + if(library.contains("libmkl_rt") || library.contains("libopenblas")) { + blasPathAndHint = " from the path " + library; + break; + } + } + } catch (NoSuchFieldException | SecurityException | IllegalArgumentException | IllegalAccessException e) { + LOG.debug("Error while finding list of native libraries:" + e.getMessage()); + } } - return isSystemMLLoaded; + return blasPathAndHint; } public static int getMaxNumThreads() { @@ -186,17 +251,14 @@ public class NativeHelper { return maxNumThreads; } - - private static boolean isMKLAvailable(String customLibPath) { - return loadBLAS(customLibPath, "mkl_rt", null); - } - - private static boolean isOpenBLASAvailable(String customLibPath) { - if(!loadBLAS(customLibPath, "gomp", "gomp required for loading OpenBLAS-enabled SystemML library")) - return false; - return loadBLAS(customLibPath, "openblas", null); - } - + /** + * Attempts to load native BLAS + * + * @param customLibPath can be null (if we want to only want to use LD_LIBRARY_PATH), else the + * @param blas can be gomp, openblas or mkl_rt + * @param optionalMsg message for debugging + * @return true if successfully loaded BLAS + */ private static boolean loadBLAS(String customLibPath, String blas, String optionalMsg) { // First attempt to load from custom library path if(customLibPath != null) { @@ -219,8 +281,6 @@ public class NativeHelper { return true; } catch (UnsatisfiedLinkError e) { - if(!hintOnFailures.contains(blas)) - hintOnFailures = hintOnFailures + blas + " "; if(optionalMsg != null) LOG.debug("Unable to load " + blas + "(" + optionalMsg + "):" + e.getMessage()); else http://git-wip-us.apache.org/repos/asf/systemml/blob/fe9b023c/src/main/java/org/apache/sysml/utils/Statistics.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/sysml/utils/Statistics.java b/src/main/java/org/apache/sysml/utils/Statistics.java index 44bb232..762c167 100644 --- a/src/main/java/org/apache/sysml/utils/Statistics.java +++ b/src/main/java/org/apache/sysml/utils/Statistics.java @@ -774,8 +774,8 @@ public class Statistics //show extended caching/compilation statistics if( DMLScript.STATISTICS ) { - if(NativeHelper.blasType != null) { - String blas = NativeHelper.blasType != null ? NativeHelper.blasType : ""; + if(NativeHelper.CURRENT_NATIVE_BLAS_STATE == NativeHelper.NativeBlasState.SUCCESSFULLY_LOADED_NATIVE_BLAS_AND_IN_USE) { + String blas = NativeHelper.getCurrentBLAS(); sb.append("Native " + blas + " calls (dense mult/conv/bwdF/bwdD):\t" + numNativeLibMatrixMultCalls.longValue() + "/" + numNativeConv2dCalls.longValue() + "/" + numNativeConv2dBwdFilterCalls.longValue() + "/" + numNativeConv2dBwdDataCalls.longValue() + ".\n");
