chore: import upstream snapshot with attribution

This commit is contained in:
wehub-resource-sync
2026-07-13 12:47:05 +08:00
commit 4f3b7da785
7394 changed files with 2005594 additions and 0 deletions
+264
View File
@@ -0,0 +1,264 @@
# Created by https://www.gitignore.io/api/java,linux,macos,maven,windows,eclipse,netbeans,intellij
### Eclipse ###
.metadata
bin/
tmp/
*.tmp
*.bak
*.swp
*~.nib
local.properties
.settings/
.loadpath
.recommenders
# Eclipse Core
.project
# External tool builders
.externalToolBuilders/
# Locally stored "Eclipse launch configurations"
*.launch
# PyDev specific (Python IDE for Eclipse)
*.pydevproject
# CDT-specific (C/C++ Development Tooling)
.cproject
# JDT-specific (Eclipse Java Development Tools)
.classpath
# Java annotation processor (APT)
.factorypath
# PDT-specific (PHP Development Tools)
.buildpath
# sbteclipse plugin
.target
# Tern plugin
.tern-project
# TeXlipse plugin
.texlipse
# STS (Spring Tool Suite)
.springBeans
# Code Recommenders
.recommenders/
# Scala IDE specific (Scala & Java development for Eclipse)
.cache-main
.scala_dependencies
.worksheet
### Intellij ###
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
# User-specific stuff:
.idea/**/workspace.xml
.idea/**/tasks.xml
.idea/dictionaries
# Sensitive or high-churn files:
.idea/**/dataSources/
.idea/**/dataSources.ids
.idea/**/dataSources.xml
.idea/**/dataSources.local.xml
.idea/**/sqlDataSources.xml
.idea/**/dynamic.xml
.idea/**/uiDesigner.xml
# Gradle:
.idea/**/gradle.xml
.idea/**/libraries
# Mongo Explorer plugin:
.idea/**/mongoSettings.xml
## File-based project format:
*.iws
## Plugin-specific files:
# IntelliJ
/out/
# mpeltonen/sbt-idea plugin
.idea_modules/
# JIRA plugin
atlassian-ide-plugin.xml
# Cursive Clojure plugin
.idea/replstate.xml
# Crashlytics plugin (for Android Studio and IntelliJ)
com_crashlytics_export_strings.xml
crashlytics.properties
crashlytics-build.properties
fabric.properties
### Intellij Patch ###
# Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721
# *.iml
# modules.xml
# .idea/misc.xml
# *.ipr
# Sonarlint plugin
.idea/sonarlint
### Java ###
# Compiled class file
*.class
# Log file
*.log
# BlueJ files
*.ctxt
# Mobile Tools for Java (J2ME)
.mtj.tmp/
# Package Files #
*.jar
*.war
*.ear
*.zip
*.tar.gz
*.rar
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
hs_err_pid*
### Linux ###
*~
# temporary files which can be created if a process still has a handle open of a deleted file
.fuse_hidden*
# KDE directory preferences
.directory
# Linux trash folder which might appear on any partition or disk
.Trash-*
# .nfs files are created when an open file is removed but is still being accessed
.nfs*
### macOS ###
*.DS_Store
.AppleDouble
.LSOverride
# Icon must end with two \r
Icon
# Thumbnails
._*
# Files that might appear in the root of a volume
.DocumentRevisions-V100
.fseventsd
.Spotlight-V100
.TemporaryItems
.Trashes
.VolumeIcon.icns
.com.apple.timemachine.donotpresent
# Directories potentially created on remote AFP share
.AppleDB
.AppleDesktop
Network Trash Folder
Temporary Items
.apdisk
### Maven ###
target/
pom.xml.tag
pom.xml.releaseBackup
pom.xml.versionsBackup
pom.xml.next
release.properties
dependency-reduced-pom.xml
buildNumber.properties
.mvn/timing.properties
# Avoid ignoring Maven wrapper jar file (.jar files are usually ignored)
!/.mvn/wrapper/maven-wrapper.jar
### NetBeans ###
nbproject/private/
build/
nbbuild/
dist/
nbdist/
.nb-gradle/
### VSCode ###
.vscode/
### Windows ###
# Windows thumbnail cache files
Thumbs.db
ehthumbs.db
ehthumbs_vista.db
# Folder config file
Desktop.ini
# Recycle Bin used on file shares
$RECYCLE.BIN/
# Windows Installer files
*.cab
*.msi
*.msm
*.msp
# Windows shortcuts
*.lnk
# End of https://www.gitignore.io/api/java,linux,macos,maven,windows,eclipse,netbeans,intellij
# Specific for Nd4j
*.md5
*.pom
*.sha1
*.ser
*.so
application.home_IS_UNDEFINEiD
*.jpg
*.png
*.bin
*.out
*.iml
.idea/
*.prefs
model-saver-*
mnist-pretrain-dbn.bin-*
deeplearning4j-scaleout/deeplearning4j-aws/src/main/java/org/deeplearning4j/aws/ec2/provision/Ec2CommandRunner.java.settings/
*.dylib
lib/
nd4j-shade/jackson/dependency-reduced-pom.xml
!nd4j-backends/nd4j-api-parent/nd4j-api/src/main/resources/bin
nd4j-backends/nd4j-backend-impls/nd4j-native/src/test/resources/writeNumpy.csv
nd4j-backends/nd4j-tests/src/test/resources/tf_graphs/examples/**/data-all*
nd4j-backends/nd4j-tests/src/test/resources/tf_graphs/examples/**/checkpoint
*-git.properties
nd4j-backends/nd4j-api-parent/nd4j-api/src/main/java/onnx/
nd4j-backends/nd4j-api-parent/nd4j-api/src/main/java/org/tensorflow/
nd4j-backends/nd4j-api-parent/nd4j-api/src/main/java/tensorflow/
@@ -0,0 +1,421 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ /* ******************************************************************************
~ *
~ *
~ * This program and the accompanying materials are made available under the
~ * terms of the Apache License, Version 2.0 which is available at
~ * https://www.apache.org/licenses/LICENSE-2.0.
~ *
~ * See the NOTICE file distributed with this work for additional
~ * information regarding copyright ownership.
~ * Unless required by applicable law or agreed to in writing, software
~ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
~ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
~ * License for the specific language governing permissions and limitations
~ * under the License.
~ *
~ * SPDX-License-Identifier: Apache-2.0
~ ******************************************************************************/
-->
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.eclipse.deeplearning4j</groupId>
<artifactId>nd4j-api-parent</artifactId>
<version>1.0.0-SNAPSHOT</version>
</parent>
<artifactId>nd4j-api</artifactId>
<name>nd4j-api</name>
<properties>
<byteunits.version>0.9.1</byteunits.version>
<neoitertools.version>1.0.0</neoitertools.version>
<module.name>nd4j.api</module.name>
<cuda.version>12.3</cuda.version>
<cudnn.version>8.9</cudnn.version>
<libnd4j.platform>${javacpp.platform}</libnd4j.platform>
<libnd4j.extension></libnd4j.extension>
<libnd4j.chip></libnd4j.chip>
<libnd4j.classifier>${libnd4j.platform}</libnd4j.classifier>
</properties>
<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!-- Annotation scanning for discovering UDFs -->
<dependency>
<groupId>com.dorkbox</groupId>
<artifactId>Annotations</artifactId>
<version>3.2</version>
</dependency>
<dependency>
<groupId>com.jakewharton.byteunits</groupId>
<artifactId>byteunits</artifactId>
<version>${byteunits.version}</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-math3</artifactId>
<version>${commons-math3.version}</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>${commons-lang3.version}</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-collections4</artifactId>
<version>${commons-collections4.version}</version>
</dependency>
<!-- Tensorflow import -->
<dependency>
<groupId>com.google.flatbuffers</groupId>
<artifactId>flatbuffers-java</artifactId>
<version>${flatbuffers.version}</version>
</dependency>
<!-- Note that this is shaded protobuf. We use this instead of google's version mainly due ot other systems packaging
their own older (incompatible) protobuf versions-->
<dependency>
<groupId>org.eclipse.deeplearning4j</groupId>
<artifactId>protobuf</artifactId>
<version>${project.version}</version>
</dependency>
<!-- oshi: Used for collecting system information for system info reporting -->
<dependency>
<groupId>com.github.oshi</groupId>
<artifactId>oshi-core</artifactId>
<version>${oshi.version}</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</dependency>
<!-- Thi sis to redirect jackson logging to slf4j-->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>log4j-over-slf4j</artifactId>
</dependency>
<!-- Shaded version of Jackson -->
<dependency>
<groupId>org.eclipse.deeplearning4j</groupId>
<artifactId>jackson</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>commons-net</groupId>
<artifactId>commons-net</artifactId>
<version>${commons-net.version}</version>
</dependency>
<dependency>
<groupId>net.ericaro</groupId>
<artifactId>neoitertools</artifactId>
<version>${neoitertools.version}</version>
<exclusions>
<exclusion>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.eclipse.deeplearning4j</groupId>
<artifactId>nd4j-common</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
<profiles>
<!-- Mirror the same profile logic from libnd4j -->
<profile>
<id>chip</id>
<activation>
<property>
<name>libnd4j.chip</name>
</property>
</activation>
<properties>
<libnd4j.classifier>${libnd4j.platform}-${libnd4j.chip}-${cuda.version}</libnd4j.classifier>
</properties>
</profile>
<profile>
<id>extension</id>
<activation>
<property>
<name>libnd4j.extension</name>
</property>
</activation>
<properties>
<libnd4j.classifier>${libnd4j.platform}-${libnd4j.extension}</libnd4j.classifier>
</properties>
</profile>
<!-- The main profile that includes libnd4j -->
<profile>
<id>cpu</id>
<activation>
<activeByDefault>false</activeByDefault>
</activation>
<dependencies>
<dependency>
<groupId>org.eclipse.deeplearning4j</groupId>
<artifactId>libnd4j</artifactId>
<version>${project.version}</version>
<classifier>${libnd4j.classifier}</classifier>
<type>zip</type>
<optional>true</optional>
</dependency>
</dependencies>
</profile>
<profile>
<id>cuda</id>
<activation>
<property>
<name>libnd4j.generate.flatc</name>
<value>true</value>
</property>
</activation>
<dependencies>
<dependency>
<groupId>org.eclipse.deeplearning4j</groupId>
<artifactId>libnd4j</artifactId>
<version>${project.version}</version>
<type>zip</type>
<classifier>${javacpp.platform}-cuda-${cuda.version}</classifier>
<optional>true</optional>
</dependency>
</dependencies>
</profile>
<profile>
<activation>
<!-- By default protoc provided binaries don't need a protocCommand. One is downloaded. Protoc plugin does not support
protoc yet. This means we need to use a system protoc on non intel platforms. -->
<os>
<arch>amd64</arch>
</os>
</activation>
<id>protoc-provided-binaries</id>
<build>
<plugins>
<plugin>
<groupId>com.github.os72</groupId>
<artifactId>protoc-jar-maven-plugin</artifactId>
<version>${protoc-jar-maven-plugin.version}</version>
<executions>
<execution>
<id>tensorflow</id>
<phase>generate-sources</phase>
<goals>
<goal>run</goal>
</goals>
<configuration>
<protocVersion>${google.protobuf.version}</protocVersion>
<extension>.proto</extension>
<includeDirectories>
<include>src/main/protobuf/tf</include>
<include>src/main/protobuf/onnx</include>
<include>src/main/protobuf/nd4j</include>
</includeDirectories>
<inputDirectories>
<include>src/main/protobuf/tf/tensorflow</include>
<include>src/main/protobuf/onnx</include>
<include>src/main/protobuf/nd4j</include>
</inputDirectories>
<addSources>main</addSources>
<cleanOutputFolder>false</cleanOutputFolder>
<outputDirectory>src/main/java/</outputDirectory>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>com.google.code.maven-replacer-plugin</groupId>
<artifactId>replacer</artifactId>
<version>${maven-replacer-plugin.version}</version>
<configuration>
<includes>
<include>${project.build.sourceDirectory}/org/tensorflow/**</include>
<include>${project.build.sourceDirectory}/tensorflow/**</include>
<include>${project.build.sourceDirectory}/onnx/**</include>
<include>${project.build.sourceDirectory}/org/nd4j/ir/**</include>
</includes>
<token>com.google.protobuf.</token>
<value>org.nd4j.shade.protobuf.</value>
</configuration>
<executions>
<execution>
<id>replace-imports</id>
<phase>generate-sources</phase>
<goals>
<goal>replace</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
<!-- See: https://github.com/os72/protoc-jar/issues/93 We need to use a system compiled protoc since one is not provided
by the plugin.-->
<profile>
<id>osx-aarch64-protoc</id>
<build>
<plugins>
<plugin>
<groupId>com.github.os72</groupId>
<artifactId>protoc-jar-maven-plugin</artifactId>
<executions>
<execution>
<id>tensorflow</id>
<phase>generate-sources</phase>
<goals>
<goal>run</goal>
</goals>
<configuration>
<protocVersion>${google.protobuf.version}</protocVersion>
<extension>.proto</extension>
<includeDirectories>
<include>src/main/protobuf/tf</include>
<include>src/main/protobuf/onnx</include>
<include>src/main/protobuf/nd4j</include>
</includeDirectories>
<inputDirectories>
<include>src/main/protobuf/tf/tensorflow</include>
<include>src/main/protobuf/onnx</include>
<include>src/main/protobuf/nd4j</include>
</inputDirectories>
<addSources>main</addSources>
<cleanOutputFolder>false</cleanOutputFolder>
<outputDirectory>src/main/java/</outputDirectory>
<protocCommand>protoc</protocCommand> <!-- brew install protobuf -->
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>com.google.code.maven-replacer-plugin</groupId>
<artifactId>replacer</artifactId>
<version>${maven-replacer-plugin.version}</version>
<configuration>
<includes>
<include>${project.build.sourceDirectory}/org/tensorflow/**</include>
<include>${project.build.sourceDirectory}/tensorflow/**</include>
<include>${project.build.sourceDirectory}/onnx/**</include>
<include>${project.build.sourceDirectory}/org/nd4j/ir/**</include>
</includes>
<token>com.google.protobuf.</token>
<value>org.nd4j.shade.protobuf.</value>
</configuration>
<executions>
<execution>
<id>replace-imports</id>
<phase>generate-sources</phase>
<goals>
<goal>replace</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
<activation>
<os>
<name>mac os x</name>
<arch>aarch64</arch>
<family>mac</family>
</os>
</activation>
</profile>
</profiles>
<build>
<plugins>
<plugin>
<groupId>org.moditect</groupId>
<artifactId>moditect-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-antrun-plugin</artifactId>
<version>${maven-antrun-plugin.version}</version>
<executions>
<execution>
<phase>generate-sources</phase>
<goals>
<goal>run</goal>
</goals>
<configuration>
<target>
<delete
file="${project.build.sourceDirectory}/onnx/OnnxMlProto3.java"/>
<delete
file="${project.build.sourceDirectory}/onnx/OnnxOperatorsProto3.java"/>
<delete
file="${project.build.sourceDirectory}/onnx/OnnxProto3.java"/>
</target>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>com.google.code.maven-replacer-plugin</groupId>
<artifactId>replacer</artifactId>
<version>${maven-replacer-plugin.version}</version>
<configuration>
<includes>
<include>${project.build.sourceDirectory}/org/tensorflow/**</include>
<include>${project.build.sourceDirectory}/tensorflow/**</include>
<include>${project.build.sourceDirectory}/onnx/**</include>
<include>${project.build.sourceDirectory}/org/nd4j/ir/**</include>
</includes>
<token>com.google.protobuf.</token>
<value>org.nd4j.shade.protobuf.</value>
</configuration>
<executions>
<execution>
<id>replace-imports</id>
<phase>generate-sources</phase>
<goals>
<goal>replace</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
@@ -0,0 +1,40 @@
/*
* ******************************************************************************
* *
* *
* * This program and the accompanying materials are made available under the
* * terms of the Apache License, Version 2.0 which is available at
* * https://www.apache.org/licenses/LICENSE-2.0.
* *
* * See the NOTICE file distributed with this work for additional
* * information regarding copyright ownership.
* * Unless required by applicable law or agreed to in writing, software
* * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* * License for the specific language governing permissions and limitations
* * under the License.
* *
* * SPDX-License-Identifier: Apache-2.0
* *****************************************************************************
*/
package org.nd4j;
import org.nd4j.linalg.api.ndarray.INDArray;
import java.util.List;
import java.util.Map;
public interface TFGraphRunnerService{
TFGraphRunnerService init(
List<String> inputNames,
List<String> outputNames,
byte[] graphBytes,
Map<String, INDArray> constants,
Map<String, String> inputDataTypes
);
Map<String,INDArray> run(Map<String,INDArray> inputs);
}
@@ -0,0 +1,24 @@
/*
* ******************************************************************************
* *
* *
* * This program and the accompanying materials are made available under the
* * terms of the Apache License, Version 2.0 which is available at
* * https://www.apache.org/licenses/LICENSE-2.0.
* *
* * See the NOTICE file distributed with this work for additional
* * information regarding copyright ownership.
* * Unless required by applicable law or agreed to in writing, software
* * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* * License for the specific language governing permissions and limitations
* * under the License.
* *
* * SPDX-License-Identifier: Apache-2.0
* *****************************************************************************
*/
package org.nd4j.adapters;
public interface InferenceAdapter<I, O> extends InputAdapter<I>, OutputAdapter<O> {
}
@@ -0,0 +1,32 @@
/*
* ******************************************************************************
* *
* *
* * This program and the accompanying materials are made available under the
* * terms of the Apache License, Version 2.0 which is available at
* * https://www.apache.org/licenses/LICENSE-2.0.
* *
* * See the NOTICE file distributed with this work for additional
* * information regarding copyright ownership.
* * Unless required by applicable law or agreed to in writing, software
* * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* * License for the specific language governing permissions and limitations
* * under the License.
* *
* * SPDX-License-Identifier: Apache-2.0
* *****************************************************************************
*/
package org.nd4j.adapters;
import org.nd4j.linalg.dataset.MultiDataSet;
public interface InputAdapter<I> {
/**
* This method converts input object to MultiDataSet
* @param input
* @return
*/
MultiDataSet apply(I input);
}
@@ -0,0 +1,36 @@
/*
* ******************************************************************************
* *
* *
* * This program and the accompanying materials are made available under the
* * terms of the Apache License, Version 2.0 which is available at
* * https://www.apache.org/licenses/LICENSE-2.0.
* *
* * See the NOTICE file distributed with this work for additional
* * information regarding copyright ownership.
* * Unless required by applicable law or agreed to in writing, software
* * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* * License for the specific language governing permissions and limitations
* * under the License.
* *
* * SPDX-License-Identifier: Apache-2.0
* *****************************************************************************
*/
package org.nd4j.adapters;
import org.nd4j.linalg.api.ndarray.INDArray;
import java.io.Serializable;
public interface OutputAdapter<T> extends Serializable {
/**
* This method provides conversion from multiple INDArrays to T
*
* @param outputs
* @return
*/
T apply(INDArray... outputs);
}
@@ -0,0 +1,264 @@
/* ******************************************************************************
*
*
* This program and the accompanying materials are made available under the
* terms of the Apache License, Version 2.0 which is available at
* https://www.apache.org/licenses/LICENSE-2.0.
*
* See the NOTICE file distributed with this work for additional
* information regarding copyright ownership.
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* SPDX-License-Identifier: Apache-2.0
******************************************************************************/
package org.nd4j.allocator.impl;
import java.util.*;
import java.util.concurrent.atomic.AtomicLong;
import lombok.extern.slf4j.Slf4j;
import lombok.val;
import org.nd4j.linalg.factory.Nd4j;
import org.nd4j.nativeblas.NativeOpsHolder;
@Slf4j
public class MemoryTracker {
private List<AtomicLong> allocatedPerDevice = new ArrayList<>();
private List<AtomicLong> cachedPerDevice = new ArrayList<>();
private List<AtomicLong> totalPerDevice = new ArrayList<>();
private List<AtomicLong> freePerDevice = new ArrayList<>();
private List<AtomicLong> workspacesPerDevice = new ArrayList<>();
private AtomicLong cachedHost = new AtomicLong(0);
private AtomicLong allocatedHost = new AtomicLong(0);
private final static MemoryTracker INSTANCE = new MemoryTracker();
public MemoryTracker() {
for (int i = 0; i < Nd4j.getAffinityManager().getNumberOfDevices(); ++i) {
allocatedPerDevice.add(i, new AtomicLong(0));
cachedPerDevice.add(i, new AtomicLong(0));
workspacesPerDevice.add(i, new AtomicLong(0));
totalPerDevice.add(i, new AtomicLong(NativeOpsHolder.getInstance().getDeviceNativeOps().getDeviceTotalMemory(i)));
val f = new AtomicLong(NativeOpsHolder.getInstance().getDeviceNativeOps().getDeviceFreeMemory(i));
freePerDevice.add(i, f);
}
}
/**
* toString() overview of every device's current status including available memory,
* number of workspaces per device, free memory per device, total memory available for a device
* @return
*/
public String memoryPerDevice() {
StringBuilder stringBuilder = new StringBuilder();
for(int i = 0; i < Nd4j.getAffinityManager().getNumberOfDevices(); i++) {
stringBuilder.append("------Device: " + i + "---------------\n");
stringBuilder.append("Allocated on device: " + allocatedPerDevice.get(i).get() + "\n");
stringBuilder.append("Total workspace memory allocated for device: " + workspacesPerDevice.get(i).get() + "\n");
stringBuilder.append("Cached memory for device: " + cachedPerDevice.get(i).get() + "\n");
stringBuilder.append("Total device memory available: " + totalPerDevice.get(i).get() + "\n");
stringBuilder.append("Free total memory for device: " + freePerDevice.get(i).get() + "\n");
stringBuilder.append("-----------------------------------------\n");
}
return stringBuilder.toString();
}
public static MemoryTracker getInstance() {
return INSTANCE;
}
public long getAllocatedAmount(int deviceId) {
return allocatedPerDevice.get(deviceId).get();
}
public long getCachedAmount(int deviceId) {
return cachedPerDevice.get(deviceId).get();
}
/**
* This method returns number of bytes currently cached from host memory
* @return
*/
public long getCachedHostAmount() {
return cachedHost.get();
}
/**
* This method returns number of bytes currently allocated from host memory
* @return
*/
public long getAllocatedHostAmount() {
return allocatedHost.get();
}
/**
* This method returns number of bytes allocated and cached in host ram
* @return
*/
public long getActiveHostAmount() {
return getAllocatedHostAmount() + getCachedHostAmount();
}
public void incrementCachedHostAmount(long numBytes) {
cachedHost.addAndGet(numBytes);
}
public void incrementAllocatedHostAmount(long numBytes) {
allocatedHost.addAndGet(numBytes);
}
public void decrementCachedHostAmount(long numBytes) {
cachedHost.addAndGet(-numBytes);
}
public void decrementAllocatedHostAmount(long numBytes) {
allocatedHost.addAndGet(-numBytes);
}
public long getWorkspaceAllocatedAmount(int deviceId) {
return workspacesPerDevice.get(deviceId).get();
}
public long getTotalMemory(int deviceId) {
return totalPerDevice.get(deviceId).get();
}
public long getFreeMemory(int deviceId) {
return freePerDevice.get(deviceId).get();
}
/**
* This method returns approximate free memory on specified device
* @param deviceId
* @return
*/
public long getApproximateFreeMemory(int deviceId) {
val externalAllocations = getTotalMemory(deviceId) - getFreeMemory(deviceId);
val active = getActiveMemory(deviceId);
val free = getTotalMemory(deviceId) - (active + externalAllocations);
return free;
}
/**
* This method returns precise amount of free memory on specified device
* @param deviceId
* @return
*/
public long getPreciseFreeMemory(int deviceId) {
// we refresh free memory on device
val extFree =Nd4j.getNativeOps().getDeviceFreeMemory(deviceId);
return extFree;
}
/**
* This method returns delta between total memory and free memory
* @param deviceId
* @return
*/
public long getUsableMemory(int deviceId) {
return getTotalMemory(deviceId) - getFreeMemory(deviceId);
}
/**
* This method returns total amount of device memory allocated on specified device
*
* Includes: workspace memory, cached memory, regular memory
* @param deviceId
* @return
*/
public long getActiveMemory(int deviceId) {
return getWorkspaceAllocatedAmount(deviceId) + getAllocatedAmount(deviceId) + getCachedAmount(deviceId);
}
/**
* This method returns amount of memory that relies on JVM GC
*
* Includes: cached memory, regular allocated memory
*
* @param deviceId
* @return
*/
public long getManagedMemory(int deviceId) {
return getAllocatedAmount(deviceId) + getCachedAmount(deviceId);
}
/**
* This method increments amount of regular allocated memory
*
* @param deviceId
* @param memoryAdded
*/
public void incrementAllocatedAmount(int deviceId, long memoryAdded) {
allocatedPerDevice.get(deviceId).getAndAdd(matchBlock(memoryAdded));
}
/**
* This method increments amount of cached memory
*
* @param deviceId
* @param memoryAdded
*/
public void incrementCachedAmount(int deviceId, long memoryAdded) {
cachedPerDevice.get(deviceId).getAndAdd(matchBlock(memoryAdded));
}
/**
* This method decrements amount of regular allocated memory
*
* @param deviceId
* @param memorySubtracted
*/
public void decrementAllocatedAmount(int deviceId, long memorySubtracted) {
allocatedPerDevice.get(deviceId).getAndAdd(-matchBlock(memorySubtracted));
}
/**
* This method decrements amount of cached memory
*
* @param deviceId
* @param memorySubtracted
*/
public void decrementCachedAmount(int deviceId, long memorySubtracted) {
cachedPerDevice.get(deviceId).getAndAdd(-matchBlock(memorySubtracted));
}
/**
* This method increments amount of memory allocated within workspaces
*
* @param deviceId
* @param memoryAdded
*/
public void incrementWorkspaceAllocatedAmount(int deviceId, long memoryAdded) {
workspacesPerDevice.get(deviceId).getAndAdd(matchBlock(memoryAdded));
}
/**
* This method decrements amount of memory allocated within workspaces
*
* @param deviceId
* @param memorySubtracted
*/
public void decrementWorkspaceAmount(int deviceId, long memorySubtracted) {
workspacesPerDevice.get(deviceId).getAndAdd(-matchBlock(memorySubtracted));
}
private void setTotalPerDevice(int device, long memoryAvailable) {
totalPerDevice.add(device, new AtomicLong(memoryAvailable));
}
private long matchBlock(long numBytes) {
return numBytes;
}
}
@@ -0,0 +1,38 @@
/*
* ******************************************************************************
* *
* *
* * This program and the accompanying materials are made available under the
* * terms of the Apache License, Version 2.0 which is available at
* * https://www.apache.org/licenses/LICENSE-2.0.
* *
* * See the NOTICE file distributed with this work for additional
* * information regarding copyright ownership.
* * Unless required by applicable law or agreed to in writing, software
* * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* * License for the specific language governing permissions and limitations
* * under the License.
* *
* * SPDX-License-Identifier: Apache-2.0
* *****************************************************************************
*/
package org.nd4j.autodiff.execution.conf;
public enum ExecutionMode {
/**
* all operations will be executed sequentially
*/
SEQUENTIAL,
/**
* all operations will be following device id for execution mode selection
*/
STRICT,
/**
* all operations that can be executed in parallel - will be executed in parallel
*/
AUTO,
}
@@ -0,0 +1,76 @@
/*
* ******************************************************************************
* *
* *
* * This program and the accompanying materials are made available under the
* * terms of the Apache License, Version 2.0 which is available at
* * https://www.apache.org/licenses/LICENSE-2.0.
* *
* * See the NOTICE file distributed with this work for additional
* * information regarding copyright ownership.
* * Unless required by applicable law or agreed to in writing, software
* * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* * License for the specific language governing permissions and limitations
* * under the License.
* *
* * SPDX-License-Identifier: Apache-2.0
* *****************************************************************************
*/
package org.nd4j.autodiff.execution.conf;
import com.google.flatbuffers.FlatBufferBuilder;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.nd4j.graph.Direction;
import org.nd4j.graph.FlatConfiguration;
import org.nd4j.graph.ProfilingMode;
import org.nd4j.linalg.api.ops.executioner.OpExecutioner;
@Data
@Slf4j
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class ExecutorConfiguration {
@Builder.Default private OpExecutioner.ProfilingMode profilingMode = OpExecutioner.ProfilingMode.DISABLED;
@Builder.Default private ExecutionMode executionMode = ExecutionMode.SEQUENTIAL;
@Builder.Default private OutputMode outputMode = OutputMode.IMPLICIT;
@Builder.Default boolean gatherTimings = true;
@Builder.Default private long footprintForward = 0L;
@Builder.Default private long footprintBackward = 0L;
/**
* This method
* @param builder
* @return
*/
public int getFlatConfiguration(FlatBufferBuilder builder) {
byte prof = profilingMode == OpExecutioner.ProfilingMode.INF_PANIC ? ProfilingMode.INF_PANIC :
profilingMode == OpExecutioner.ProfilingMode.NAN_PANIC ? ProfilingMode.NAN_PANIC :
profilingMode == OpExecutioner.ProfilingMode.ANY_PANIC ? ProfilingMode.ANY_PANIC : ProfilingMode.NONE;
byte exec = executionMode == ExecutionMode.SEQUENTIAL ? org.nd4j.graph.ExecutionMode.SEQUENTIAL :
executionMode == ExecutionMode.AUTO ? org.nd4j.graph.ExecutionMode.AUTO :
executionMode == ExecutionMode.STRICT ? org.nd4j.graph.ExecutionMode.STRICT : -1;
byte outp = outputMode == OutputMode.IMPLICIT ? org.nd4j.graph.OutputMode.IMPLICIT :
outputMode == OutputMode.EXPLICIT ? org.nd4j.graph.OutputMode.EXPLICIT :
outputMode == OutputMode.EXPLICIT_AND_IMPLICIT ? org.nd4j.graph.OutputMode.EXPLICIT_AND_IMPLICIT :
outputMode == OutputMode.VARIABLE_SPACE ? org.nd4j.graph.OutputMode.VARIABLE_SPACE : -1;
if (exec == -1)
throw new UnsupportedOperationException("Unknown values were passed into configuration as ExecutionMode: [" + executionMode + "]");
if (outp == -1)
throw new UnsupportedOperationException("Unknown values were passed into configuration as OutputMode: [" + outputMode + "]");
return FlatConfiguration.createFlatConfiguration(builder, -1, prof, exec, outp, gatherTimings, footprintForward, footprintBackward, Direction.FORWARD_ONLY);
}
}
@@ -0,0 +1,43 @@
/*
* ******************************************************************************
* *
* *
* * This program and the accompanying materials are made available under the
* * terms of the Apache License, Version 2.0 which is available at
* * https://www.apache.org/licenses/LICENSE-2.0.
* *
* * See the NOTICE file distributed with this work for additional
* * information regarding copyright ownership.
* * Unless required by applicable law or agreed to in writing, software
* * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* * License for the specific language governing permissions and limitations
* * under the License.
* *
* * SPDX-License-Identifier: Apache-2.0
* *****************************************************************************
*/
package org.nd4j.autodiff.execution.conf;
public enum OutputMode {
/**
* only final nodes of graph will be returned
*/
IMPLICIT,
/**
* only declared output fields
*/
EXPLICIT,
/**
* both options enabled
*/
EXPLICIT_AND_IMPLICIT,
/**
* whole content of VariableSpace will be returned back
*/
VARIABLE_SPACE,
}
@@ -0,0 +1,158 @@
/*
* ******************************************************************************
* *
* *
* * This program and the accompanying materials are made available under the
* * terms of the Apache License, Version 2.0 which is available at
* * https://www.apache.org/licenses/LICENSE-2.0.
* *
* * See the NOTICE file distributed with this work for additional
* * information regarding copyright ownership.
* * Unless required by applicable law or agreed to in writing, software
* * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* * License for the specific language governing permissions and limitations
* * under the License.
* *
* * SPDX-License-Identifier: Apache-2.0
* *****************************************************************************
*/
package org.nd4j.autodiff.execution.input;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.NonNull;
import lombok.val;
import org.nd4j.linalg.api.ndarray.INDArray;
import org.nd4j.common.primitives.Pair;
import java.util.*;
public class Operands {
private Map<NodeDescriptor, INDArray> map = new LinkedHashMap<>();
/**
* This method allows to pass array to the node identified by its name
*
* @param id
* @param array
* @return
*/
public Operands addArgument(@NonNull String id, @NonNull INDArray array) {
map.put(NodeDescriptor.builder().name(id).build(), array);
return this;
}
/**
* This method allows to pass array to the node identified by numeric id
*
* @param id
* @param array
* @return
*/
public Operands addArgument(int id, @NonNull INDArray array) {
map.put(NodeDescriptor.builder().id(id).build(), array);
return this;
}
/**
* This method allows to pass array to multi-output node in the graph
*
* @param id
* @param index
* @param array
* @return
*/
public Operands addArgument( int id, int index, @NonNull INDArray array) {
map.put(NodeDescriptor.builder().id(id).index(index).build(), array);
return this;
}
/**
* This method allows to pass array to multi-output node in the graph
*
* @param id
* @param index
* @param array
* @return
*/
public Operands addArgument(String name, int id, int index, @NonNull INDArray array) {
map.put(NodeDescriptor.builder().name(name).id(id).index(index).build(), array);
return this;
}
/**
* This method returns array identified its name
* @param name
* @return
*/
public INDArray getById(@NonNull String name) {
return map.get(NodeDescriptor.builder().name(name).build());
}
/**
* This method returns array identified its numeric id
* @param name
* @return
*/
public INDArray getById(int id) {
return map.get(NodeDescriptor.builder().id(id).build());
}
/**
* This method returns array identified its numeric id and index
* @param name
* @return
*/
public INDArray getById(int id, int index) {
return map.get(NodeDescriptor.builder().id(id).index(index).build());
}
/**
* This method return operands as array, in order of addition
* @return
*/
public INDArray[] asArray() {
val val = map.values();
val res = new INDArray[val.size()];
int cnt = 0;
for (val v: val)
res[cnt++] = v;
return res;
}
/**
* This method returns contents of this entity as collection of key->value pairs
* @return
*/
public Collection<Pair<NodeDescriptor, INDArray>> asCollection() {
val c = new HashSet<Pair<NodeDescriptor, INDArray>>();
for (val k: map.keySet())
c.add(Pair.makePair(k, map.get(k)));
return c;
}
/**
* This method returns number of values in this entity
* @return
*/
public int size() {
return map.size();
}
@NoArgsConstructor
@AllArgsConstructor
@Builder
@Data
public static class NodeDescriptor {
private String name;
private int id;
private int index;
}
}
@@ -0,0 +1,110 @@
/*
* ******************************************************************************
* *
* *
* * This program and the accompanying materials are made available under the
* * terms of the Apache License, Version 2.0 which is available at
* * https://www.apache.org/licenses/LICENSE-2.0.
* *
* * See the NOTICE file distributed with this work for additional
* * information regarding copyright ownership.
* * Unless required by applicable law or agreed to in writing, software
* * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* * License for the specific language governing permissions and limitations
* * under the License.
* *
* * SPDX-License-Identifier: Apache-2.0
* *****************************************************************************
*/
package org.nd4j.autodiff.listeners;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.EqualsAndHashCode;
import lombok.NonNull;
import lombok.Setter;
import lombok.ToString;
import org.nd4j.autodiff.samediff.internal.FrameIter;
@AllArgsConstructor
@EqualsAndHashCode
@ToString
@Builder
@Setter
public class At {
private int epoch;
private int iteration;
private int trainingThreadNum;
private long javaThreadNum;
private FrameIter frameIter;
private Operation operation;
/**
* @return A new instance with everything set to 0, and operation set to INFERENCE
*/
public static At defaultAt(){
return new At(0, 0, 0, 0, null, Operation.INFERENCE);
}
/**
* @param op Operation
* @return A new instance with everything set to 0, except for the specified operation
*/
public static At defaultAt(@NonNull Operation op){
return new At(0, 0, 0, 0, null, op);
}
/**
* @return The current training epoch
*/
public int epoch(){
return epoch;
}
/**
* @return The current training iteration
*/
public int iteration(){
return iteration;
}
/**
* @return The number of the SameDiff thread
*/
public int trainingThreadNum(){
return trainingThreadNum;
}
/**
* @return The Java/JVM thread number for training
*/
public long javaThreadNum(){
return javaThreadNum;
}
/**
* @return The current operation
*/
public Operation operation(){
return operation;
}
/**
* @return A copy of the current At instance
*/
public At copy(){
return new At(epoch, iteration, trainingThreadNum, javaThreadNum, frameIter, operation);
}
/**
* @param operation Operation to set in the new instance
* @return A copy of the current instance, but with the specified operation
*/
public At copy(Operation operation){
return new At(epoch, iteration, trainingThreadNum, javaThreadNum, frameIter, operation);
}
}
@@ -0,0 +1,147 @@
/*
* ******************************************************************************
* *
* *
* * This program and the accompanying materials are made available under the
* * terms of the Apache License, Version 2.0 which is available at
* * https://www.apache.org/licenses/LICENSE-2.0.
* *
* * See the NOTICE file distributed with this work for additional
* * information regarding copyright ownership.
* * Unless required by applicable law or agreed to in writing, software
* * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* * License for the specific language governing permissions and limitations
* * under the License.
* *
* * SPDX-License-Identifier: Apache-2.0
* *****************************************************************************
*/
package org.nd4j.autodiff.listeners;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.nd4j.autodiff.listeners.records.EvaluationRecord;
import org.nd4j.autodiff.listeners.records.LossCurve;
import org.nd4j.autodiff.samediff.SameDiff;
import org.nd4j.autodiff.samediff.internal.SameDiffOp;
import org.nd4j.evaluation.IEvaluation;
import org.nd4j.linalg.api.ndarray.INDArray;
import org.nd4j.linalg.dataset.api.MultiDataSet;
public abstract class BaseEvaluationListener extends BaseListener {
private Map<String, List<IEvaluation>> trainingEvaluations = new HashMap<>();
private Map<String, List<IEvaluation>> validationEvaluations = new HashMap<>();
/**
* Return the requested evaluations. New instances of these evaluations will be made each time they are used
*/
public abstract ListenerEvaluations evaluations();
@Override
public final ListenerVariables requiredVariables(SameDiff sd) {
return evaluations().requiredVariables().merge(otherRequiredVariables(sd));
}
/**
* Return any requested variables that are not part of the evaluations
*/
public ListenerVariables otherRequiredVariables(SameDiff sd){
return ListenerVariables.empty();
}
@Override
public final void epochStart(SameDiff sd, At at) {
trainingEvaluations = new HashMap<>();
for(Map.Entry<String, List<IEvaluation>> entry : evaluations().trainEvaluations().entrySet()){
List<IEvaluation> evals = new ArrayList<>();
for(IEvaluation ie : entry.getValue())
evals.add(ie.newInstance());
trainingEvaluations.put(entry.getKey(), evals);
}
validationEvaluations = new HashMap<>();
for(Map.Entry<String, List<IEvaluation>> entry : evaluations().validationEvaluations().entrySet()){
List<IEvaluation> evals = new ArrayList<>();
for(IEvaluation ie : entry.getValue())
evals.add(ie.newInstance());
validationEvaluations.put(entry.getKey(), evals);
}
epochStartEvaluations(sd, at);
}
/**
* See {@link Listener#epochStart(SameDiff, At)}
*/
public void epochStartEvaluations(SameDiff sd, At at){
//No op
}
@Override
public final ListenerResponse epochEnd(SameDiff sd, At at, LossCurve lossCurve, long epochTimeMillis) {
return epochEndEvaluations(sd, at, lossCurve, epochTimeMillis, new EvaluationRecord(trainingEvaluations));
}
/**
* See {@link Listener#epochEnd(SameDiff, At, LossCurve, long)}, also provided the requested evaluations
*/
public ListenerResponse epochEndEvaluations(SameDiff sd, At at, LossCurve lossCurve, long epochTimeMillis, EvaluationRecord evaluations) {
//No op
return ListenerResponse.CONTINUE;
}
@Override
public final ListenerResponse validationDone(SameDiff sd, At at, long validationTimeMillis) {
return validationDoneEvaluations(sd, at, validationTimeMillis, new EvaluationRecord(validationEvaluations));
}
/**
* See {@link Listener#validationDone(SameDiff, At, long)}, also provided the requested evaluations
*/
public ListenerResponse validationDoneEvaluations(SameDiff sd, At at, long validationTimeMillis, EvaluationRecord evaluations) {
//No op
return ListenerResponse.CONTINUE;
}
@Override
public final void activationAvailable(SameDiff sd, At at, MultiDataSet batch, SameDiffOp op, String varName,
INDArray activation) {
if(at.operation() == Operation.TRAINING) {
if (trainingEvaluations.containsKey(varName)) {
INDArray labels = batch.getLabels(evaluations().trainEvaluationLabels().get(varName));
INDArray mask = batch.getLabelsMaskArray(evaluations().trainEvaluationLabels().get(varName));
for (IEvaluation e : trainingEvaluations.get(varName))
e.eval(labels, activation, mask);
}
} else if(at.operation() == Operation.TRAINING_VALIDATION) {
if (validationEvaluations.containsKey(varName)) {
INDArray labels = batch.getLabels(evaluations().validationEvaluationLabels().get(varName));
INDArray mask = batch.getLabelsMaskArray(evaluations().validationEvaluationLabels().get(varName));
for (IEvaluation e : validationEvaluations.get(varName))
e.eval(labels, activation, mask);
}
}
activationAvailableEvaluations(sd, at, batch, op, varName, activation);
}
/**
* See {@link Listener#activationAvailable(SameDiff, At, MultiDataSet, SameDiffOp, String, INDArray)}
*/
public void activationAvailableEvaluations(SameDiff sd, At at, MultiDataSet batch, SameDiffOp op, String varName,
INDArray activation){
//No op
}
}
@@ -0,0 +1,95 @@
/*
* ******************************************************************************
* *
* *
* * This program and the accompanying materials are made available under the
* * terms of the Apache License, Version 2.0 which is available at
* * https://www.apache.org/licenses/LICENSE-2.0.
* *
* * See the NOTICE file distributed with this work for additional
* * information regarding copyright ownership.
* * Unless required by applicable law or agreed to in writing, software
* * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* * License for the specific language governing permissions and limitations
* * under the License.
* *
* * SPDX-License-Identifier: Apache-2.0
* *****************************************************************************
*/
package org.nd4j.autodiff.listeners;
import org.nd4j.autodiff.listeners.records.LossCurve;
import org.nd4j.autodiff.samediff.SameDiff;
import org.nd4j.autodiff.samediff.internal.SameDiffOp;
import org.nd4j.autodiff.samediff.internal.Variable;
import org.nd4j.linalg.api.ndarray.INDArray;
import org.nd4j.linalg.api.ops.OpContext;
import org.nd4j.linalg.dataset.api.MultiDataSet;
public abstract class BaseListener implements Listener {
@Override
public ListenerVariables requiredVariables(SameDiff sd){
return ListenerVariables.empty();
}
@Override
public void epochStart(SameDiff sd, At at) {
//No op
}
@Override
public ListenerResponse epochEnd(SameDiff sd, At at, LossCurve lossCurve, long epochTimeMillis) {
return ListenerResponse.CONTINUE;
}
@Override
public ListenerResponse validationDone(SameDiff sd, At at, long validationTimeMillis) {
//No op
return ListenerResponse.CONTINUE;
}
@Override
public void iterationStart(SameDiff sd, At at, MultiDataSet data, long etlMs) {
//No op
}
@Override
public void iterationDone(SameDiff sd, At at, MultiDataSet dataSet, Loss loss) {
//No op
}
@Override
public void operationStart(SameDiff sd, Operation op) {
//No op
}
@Override
public void operationEnd(SameDiff sd, Operation op) {
//No op
}
@Override
public void preOpExecution(SameDiff sd, At at, SameDiffOp op, OpContext opContext) {
//No op
}
@Override
public void opExecution(SameDiff sd, At at, MultiDataSet batch, SameDiffOp op, OpContext opContext, INDArray[] outputs) {
//No op
}
@Override
public void activationAvailable(SameDiff sd, At at, MultiDataSet batch, SameDiffOp op, String varName,
INDArray activation) {
//No op
}
@Override
public void preUpdate(SameDiff sd, At at, Variable v, INDArray update) {
//No op
}
}
@@ -0,0 +1,166 @@
/*
* ******************************************************************************
* *
* *
* * This program and the accompanying materials are made available under the
* * terms of the Apache License, Version 2.0 which is available at
* * https://www.apache.org/licenses/LICENSE-2.0.
* *
* * See the NOTICE file distributed with this work for additional
* * information regarding copyright ownership.
* * Unless required by applicable law or agreed to in writing, software
* * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* * License for the specific language governing permissions and limitations
* * under the License.
* *
* * SPDX-License-Identifier: Apache-2.0
* *****************************************************************************
*/
package org.nd4j.autodiff.listeners;
import org.nd4j.autodiff.listeners.records.LossCurve;
import org.nd4j.autodiff.samediff.SameDiff;
import org.nd4j.autodiff.samediff.internal.SameDiffOp;
import org.nd4j.autodiff.samediff.internal.Variable;
import org.nd4j.linalg.api.ndarray.INDArray;
import org.nd4j.linalg.api.ops.OpContext;
import org.nd4j.linalg.dataset.api.MultiDataSet;
public interface Listener {
/**
* Required variables for this listener.
* <p>
* Used to ensure these variables end up in the minimum required subgraph calculated by {@link org.nd4j.autodiff.samediff.internal.InferenceSession}.
* Otherwise, if the variables weren't required by a loss variable, they would not be calculated.
* <p>
* Any variables in here are guaranteed to have {@link Listener#activationAvailable(SameDiff, At, MultiDataSet, SameDiffOp, String, INDArray)}
* called for them, regardless of whether they would normally be calculated or not.
*/
ListenerVariables requiredVariables(SameDiff sd);
/**
* Returns whether this listener is active during the given operation. If this returns false for the given operation,
* those listener methods will not be called.
*/
boolean isActive(Operation operation);
/**
* Called at the start of every epoch, when fitting from an iterator
*
* @param sd The SameDiff instance
* @param at Current iteration/epoch etc
*/
void epochStart(SameDiff sd, At at);
/**
* Called at the end of every epoch, when fitting from an iterator
*
* @param sd The SameDiff instance
* @param at Current iteration/epoch etc
* @param lossCurve The losses so far
* @param epochTimeMillis How long this epoch took
* @return ListenerResponse.STOP to stop training, CONTINUE or null to continue
*/
ListenerResponse epochEnd(SameDiff sd, At at, LossCurve lossCurve, long epochTimeMillis);
/**
* Called after the end of every epoch, once validation evaluation is done, when training
*
* @param sd The SameDiff instance
* @param at Current iteration/epoch etc
* @param validationTimeMillis How long validation took for this epoch
* @return ListenerResponse.STOP to stop training, CONTINUE or null to continue
*/
ListenerResponse validationDone(SameDiff sd, At at, long validationTimeMillis);
/**
* Called at the start of every iteration (minibatch), before any operations have been executed
*
* @param sd The SameDiff instance
* @param at Current iteration/epoch etc
*/
void iterationStart(SameDiff sd, At at, MultiDataSet data, long etlTimeMs);
/**
* Called at the end of every iteration, after all operations (including updating parameters) has been completed
*
* @param sd The SameDiff instance
* @param at Current iteration/epoch etc
* @param dataSet The current dataset (minibatch) used for training
* @param loss The loss value for the current minibatch. Will be null except for during training
*/
void iterationDone(SameDiff sd, At at, MultiDataSet dataSet, Loss loss);
/**
* Called at the start of an operation, e.g. training or validation
*
* @param sd The SameDiff instance
* @param op The operation being started
*/
void operationStart(SameDiff sd, Operation op);
/**
* Called at the end of an operation, e.g. training or validation
*
* @param sd The SameDiff instance
* @param op The operation being started
*/
void operationEnd(SameDiff sd, Operation op);
/**
* Called just before each operation is executed (native code called, etc) - after all inputs etc have been set
*
* @param sd The SameDiff instance
* @param at Current iteration/epoch etc
* @param op Operation that has just been executed
*/
void preOpExecution(SameDiff sd, At at, SameDiffOp op, OpContext opContext);
/**
* Called at the end of each operation execution<br>
* <p>
* Note: Outputs will most likely be freed later, use detach() if you need to save it.
*
* @param sd The SameDiff instance
* @param at Current iteration/epoch etc
* @param batch The batch's input data. May be null if not called with a batch
* @param op Operation that has just been executed
* @param outputs The output arrays for the just-executed operation
*/
void opExecution(SameDiff sd, At at, MultiDataSet batch, SameDiffOp op, OpContext opContext, INDArray[] outputs);
/**
* Called when any activation becomes available.
* <p>
* The activation will most likely be freed later, use dup() if you need to save it.<br>
* <br>
* Note that this method will be called when any activation becomes available, not just ones from {@link #requiredVariables(SameDiff)}<br>
* It is guaranteed to be called for variables from requiredVariables().<br>
* <br>
* Note that the activations here overlap with {@link #opExecution(SameDiff, At, MultiDataSet, SameDiffOp, OpContext, INDArray[])} -
* both contain the same information/arrays
*
* @param sd The SameDiff instance
* @param at Current iteration/epoch etc
* @param batch The batch's input data. May be null if not called with a batch
* @param op Operation that has just been executed
* @param varName The name of the variable
* @param activation The variable's activation
*/
void activationAvailable(SameDiff sd, At at, MultiDataSet batch, SameDiffOp op, String varName, INDArray activation);
/**
* Called just before each parameter is to be updated - i.e., just before each parameter is modified.
*
* @param sd SameDiff instance
* @param at The current iteration/epoch etc
* @param v Variable about to be updated during backprop
* @param update The array representing the update (i.e., the gradient after applying learning rate, momentum, etc)
*/
void preUpdate(SameDiff sd, At at, Variable v, INDArray update);
}
@@ -0,0 +1,215 @@
/*
* ******************************************************************************
* *
* *
* * This program and the accompanying materials are made available under the
* * terms of the Apache License, Version 2.0 which is available at
* * https://www.apache.org/licenses/LICENSE-2.0.
* *
* * See the NOTICE file distributed with this work for additional
* * information regarding copyright ownership.
* * Unless required by applicable law or agreed to in writing, software
* * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* * License for the specific language governing permissions and limitations
* * under the License.
* *
* * SPDX-License-Identifier: Apache-2.0
* *****************************************************************************
*/
package org.nd4j.autodiff.listeners;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.NonNull;
import lombok.Setter;
import org.nd4j.autodiff.samediff.SDVariable;
import org.nd4j.common.base.Preconditions;
import org.nd4j.evaluation.IEvaluation;
@Getter
public class ListenerEvaluations {
private Map<String, List<IEvaluation>> trainEvaluations;
private Map<String, Integer> trainEvaluationLabels;
private Map<String, List<IEvaluation>> validationEvaluations;
private Map<String, Integer> validationEvaluationLabels;
public ListenerEvaluations(Map<String, List<IEvaluation>> trainEvaluations,
Map<String, Integer> trainEvaluationLabels, Map<String, List<IEvaluation>> validationEvaluations,
Map<String, Integer> validationEvaluationLabels) {
this.trainEvaluations = trainEvaluations;
this.trainEvaluationLabels = trainEvaluationLabels;
this.validationEvaluations = validationEvaluations;
this.validationEvaluationLabels = validationEvaluationLabels;
Preconditions.checkArgument(trainEvaluations.keySet().equals(trainEvaluationLabels.keySet()),
"Must specify a label index for each train evaluation. Expected: %s, got: %s",
trainEvaluations.keySet(), trainEvaluationLabels.keySet());
Preconditions.checkArgument(validationEvaluations.keySet().equals(validationEvaluationLabels.keySet()),
"Must specify a label index for each validation evaluation. Expected: %s, got: %s",
validationEvaluations.keySet(), validationEvaluationLabels.keySet());
}
private ListenerEvaluations() {
}
public static Builder builder() {
return new Builder();
}
/**
* Get the requested training evaluations
*/
public Map<String, List<IEvaluation>> trainEvaluations() {
return trainEvaluations;
}
/**
* Get the label indices for the requested training evaluations
*/
public Map<String, Integer> trainEvaluationLabels() {
return trainEvaluationLabels;
}
/**
* Get the requested validation evaluations
*/
public Map<String, List<IEvaluation>> validationEvaluations() {
return validationEvaluations;
}
/**
* Get the label indices for the requested validation evaluations
*/
public Map<String, Integer> validationEvaluationLabels() {
return validationEvaluationLabels;
}
/**
* Get the required variables for these evaluations
*/
public ListenerVariables requiredVariables() {
return new ListenerVariables(trainEvaluations.keySet(), validationEvaluations.keySet(),
new HashSet<String>(), new HashSet<String>());
}
/**
* @return true if there are no requested evaluations
*/
public boolean isEmpty() {
return trainEvaluations.isEmpty() && validationEvaluations.isEmpty();
}
@NoArgsConstructor
@Getter
@Setter
public static class Builder {
private Map<String, List<IEvaluation>> trainEvaluations = new HashMap<>();
private Map<String, Integer> trainEvaluationLabels = new HashMap<>();
private Map<String, List<IEvaluation>> validationEvaluations = new HashMap<>();
private Map<String, Integer> validationEvaluationLabels = new HashMap<>();
private void addEvaluations(boolean validation, @NonNull Map<String, List<IEvaluation>> evaluationMap, @NonNull Map<String, Integer> labelMap,
@NonNull String variableName, int labelIndex, @NonNull IEvaluation... evaluations) {
if (evaluationMap.containsKey(variableName) && labelMap.get(variableName) != labelIndex) {
String s;
if (validation) {
s = "This ListenerEvaluations.Builder already has validation evaluations for ";
} else {
s = "This ListenerEvaluations.Builder already has train evaluations for ";
}
throw new IllegalArgumentException(s + "variable " +
variableName + " with label index " + labelIndex + ". You can't add " +
" evaluations with a different label index. Got label index " + labelIndex);
}
if (evaluationMap.containsKey(variableName)) {
evaluationMap.get(variableName).addAll(Arrays.asList(evaluations));
} else {
evaluationMap.put(variableName, Arrays.asList(evaluations));
labelMap.put(variableName, labelIndex);
}
}
/**
* Add requested training evaluations for a parm/variable
*
* @param variableName The variable to evaluate
* @param labelIndex The index of the label to evaluate against
* @param evaluations The evaluations to run
*/
public Builder trainEvaluation(@NonNull String variableName, int labelIndex, @NonNull IEvaluation... evaluations) {
addEvaluations(false, this.trainEvaluations, this.trainEvaluationLabels, variableName,
labelIndex, evaluations);
return this;
}
/**
* Add requested training evaluations for a parm/variable
*
* @param variable The variable to evaluate
* @param labelIndex The index of the label to evaluate against
* @param evaluations The evaluations to run
*/
public Builder trainEvaluation(@NonNull SDVariable variable, int labelIndex, @NonNull IEvaluation... evaluations) {
return trainEvaluation(variable.name(), labelIndex, evaluations);
}
/**
* Add requested validation evaluations for a parm/variable
*
* @param variableName The variable to evaluate
* @param labelIndex The index of the label to evaluate against
* @param evaluations The evaluations to run
*/
public Builder validationEvaluation(@NonNull String variableName, int labelIndex, @NonNull IEvaluation... evaluations) {
addEvaluations(true, this.validationEvaluations, this.validationEvaluationLabels, variableName,
labelIndex, evaluations);
return this;
}
/**
* Add requested validation evaluations for a parm/variable
*
* @param variable The variable to evaluate
* @param labelIndex The index of the label to evaluate against
* @param evaluations The evaluations to run
*/
public Builder validationEvaluation(@NonNull SDVariable variable, int labelIndex, @NonNull IEvaluation... evaluations) {
return validationEvaluation(variable.name(), labelIndex, evaluations);
}
/**
* Add requested evaluations for a parm/variable, for either training or validation
*
* @param validation Whether to add these evaluations as validation or training
* @param variableName The variable to evaluate
* @param labelIndex The index of the label to evaluate against
* @param evaluations The evaluations to run
*/
public Builder addEvaluations(boolean validation, @NonNull String variableName, int labelIndex, @NonNull IEvaluation... evaluations) {
if (validation) {
return validationEvaluation(variableName, labelIndex, evaluations);
} else {
return trainEvaluation(variableName, labelIndex, evaluations);
}
}
public ListenerEvaluations build() {
return new ListenerEvaluations(trainEvaluations, trainEvaluationLabels, validationEvaluations, validationEvaluationLabels);
}
}
}
@@ -0,0 +1,25 @@
/*
* ******************************************************************************
* *
* *
* * This program and the accompanying materials are made available under the
* * terms of the Apache License, Version 2.0 which is available at
* * https://www.apache.org/licenses/LICENSE-2.0.
* *
* * See the NOTICE file distributed with this work for additional
* * information regarding copyright ownership.
* * Unless required by applicable law or agreed to in writing, software
* * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* * License for the specific language governing permissions and limitations
* * under the License.
* *
* * SPDX-License-Identifier: Apache-2.0
* *****************************************************************************
*/
package org.nd4j.autodiff.listeners;
public enum ListenerResponse {
CONTINUE, STOP;
}
@@ -0,0 +1,233 @@
/*
* ******************************************************************************
* *
* *
* * This program and the accompanying materials are made available under the
* * terms of the Apache License, Version 2.0 which is available at
* * https://www.apache.org/licenses/LICENSE-2.0.
* *
* * See the NOTICE file distributed with this work for additional
* * information regarding copyright ownership.
* * Unless required by applicable law or agreed to in writing, software
* * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* * License for the specific language governing permissions and limitations
* * under the License.
* *
* * SPDX-License-Identifier: Apache-2.0
* *****************************************************************************
*/
package org.nd4j.autodiff.listeners;
import org.nd4j.shade.guava.collect.Sets;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import lombok.Setter;
import org.nd4j.autodiff.samediff.SDVariable;
import org.nd4j.autodiff.samediff.SameDiff;
import org.nd4j.autodiff.samediff.internal.SameDiffOp;
import org.nd4j.linalg.api.ndarray.INDArray;
import org.nd4j.linalg.dataset.api.MultiDataSet;
@RequiredArgsConstructor
@Getter
public class ListenerVariables {
public static ListenerVariables empty() {
return ListenerVariables.builder().build();
}
@NonNull
private Set<String> trainingVariables;
@NonNull
private Set<String> validationVariables;
@NonNull
private Set<String> evaluationVariables;
@NonNull
private Set<String> inferenceVariables;
public static Builder builder() {
return new Builder();
}
/**
* Get required training variables
*/
public Set<String> trainingVariables() {
return trainingVariables;
}
/**
* Get required validation variables
*/
public Set<String> validationVariables() {
return validationVariables;
}
/**
* Get required evaluation variables
*/
public Set<String> evaluationVariables() {
return evaluationVariables;
}
/**
* Get required inference variables
*/
public Set<String> inferenceVariables() {
return inferenceVariables;
}
/**
* Get required variables for specified op
*/
public Set<String> requiredVariables(Operation op) {
switch (op) {
case TRAINING:
return trainingVariables;
case TRAINING_VALIDATION:
return validationVariables;
case INFERENCE:
return inferenceVariables;
case EVALUATION:
return evaluationVariables;
}
throw new IllegalArgumentException("Unknown operation " + op);
}
private ListenerVariables() {
}
/**
* Return a new ListenerVariables that contains the variables of this ListenerVariables and of other
*/
public ListenerVariables merge(ListenerVariables other) {
return new ListenerVariables(
Sets.newHashSet(Sets.union(trainingVariables, other.trainingVariables)),
Sets.newHashSet(Sets.union(validationVariables, other.validationVariables)),
Sets.newHashSet(Sets.union(evaluationVariables, other.evaluationVariables)),
Sets.newHashSet(Sets.union(inferenceVariables, other.inferenceVariables)));
}
@NoArgsConstructor
@Getter
@Setter
public static class Builder {
@NonNull
private Set<String> trainingVariables = new HashSet<>();
@NonNull
private Set<String> validationVariables = new HashSet<>();
@NonNull
private Set<String> evaluationVariables = new HashSet<>();
@NonNull
private Set<String> inferenceVariables = new HashSet<>();
/**
* Add required variables for the specified op
*
* @param op The op to require the variable for
*/
public Builder requireVariables(@NonNull Operation op, @NonNull String... variables) {
switch (op) {
case TRAINING:
trainingVariables.addAll(Arrays.asList(variables));
break;
case TRAINING_VALIDATION:
validationVariables.addAll(Arrays.asList(variables));
break;
case INFERENCE:
inferenceVariables.addAll(Arrays.asList(variables));
break;
case EVALUATION:
evaluationVariables.addAll(Arrays.asList(variables));
break;
}
return this;
}
/**
* Add required variables for the specified op
*
* @param op The op to require the variable for
*/
public Builder requireVariables(@NonNull Operation op, @NonNull SDVariable... variables) {
String[] names = new String[variables.length];
for (int i = 0; i < variables.length; i++)
names[i] = variables[i].name();
return requireVariables(op, names);
}
/**
* Add required variables for training
*/
public Builder trainingVariables(@NonNull String... variables) {
return requireVariables(Operation.TRAINING, variables);
}
/**
* Add required variables for training
*/
public Builder trainingVariables(@NonNull SDVariable... variables) {
return requireVariables(Operation.TRAINING, variables);
}
/**
* Add required variables for validation
*/
public Builder validationVariables(@NonNull String... variables) {
return requireVariables(Operation.TRAINING_VALIDATION, variables);
}
/**
* Add required variables for validation
*/
public Builder validationVariables(@NonNull SDVariable... variables) {
return requireVariables(Operation.TRAINING_VALIDATION, variables);
}
/**
* Add required variables for inference
*/
public Builder inferenceVariables(@NonNull String... variables) {
return requireVariables(Operation.INFERENCE, variables);
}
/**
* Add required variables for inference
*/
public Builder inferenceVariables(@NonNull SDVariable... variables) {
return requireVariables(Operation.INFERENCE, variables);
}
/**
* Add required variables for evaluation
*/
public Builder evaluationVariables(@NonNull String... variables) {
return requireVariables(Operation.EVALUATION, variables);
}
/**
* Add required variables for evaluation
*/
public Builder evaluationVariables(@NonNull SDVariable... variables) {
return requireVariables(Operation.EVALUATION, variables);
}
public ListenerVariables build() {
return new ListenerVariables(trainingVariables, validationVariables, evaluationVariables, inferenceVariables);
}
}
}
@@ -0,0 +1,183 @@
/*
* ******************************************************************************
* *
* *
* * This program and the accompanying materials are made available under the
* * terms of the Apache License, Version 2.0 which is available at
* * https://www.apache.org/licenses/LICENSE-2.0.
* *
* * See the NOTICE file distributed with this work for additional
* * information regarding copyright ownership.
* * Unless required by applicable law or agreed to in writing, software
* * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* * License for the specific language governing permissions and limitations
* * under the License.
* *
* * SPDX-License-Identifier: Apache-2.0
* *****************************************************************************
*/
package org.nd4j.autodiff.listeners;
import java.util.ArrayList;
import java.util.Collections;
import lombok.Data;
import lombok.NonNull;
import org.nd4j.common.base.Preconditions;
import java.util.List;
@Data
public class Loss {
private final List<String> lossNames;
private final double[] losses;
/**
* @param lossNames Names of the losses
* @param losses Values for each loss. Must be same length as lossNames
*/
public Loss(@NonNull List<String> lossNames, @NonNull double[] losses) {
Preconditions.checkState(lossNames.size() == losses.length, "Expected equal number of loss names and loss values");
this.lossNames = lossNames;
this.losses = losses;
}
/**
* @return Number of loss values (i.e., length of lossNames and losses)
*/
public int numLosses() {
return lossNames.size();
}
/**
* @return Names of all of the loss components
*/
public List<String> lossNames() {
return lossNames;
}
/**
* @return Values corresponding to each of the losses (same order as lossNames())
*/
public double[] lossValues() {
return losses;
}
/**
* Get the specified loss by name
*
* @param lossName Name of the loss (must exist)
* @return Specified loss value
*/
public double getLoss(@NonNull String lossName) {
int idx = lossNames.indexOf(lossName);
Preconditions.checkState(idx >= 0, "No loss with name \"%s\" exists. All loss names: %s", lossName, lossNames);
return losses[idx];
}
/**
* @return The total loss (sum of all loss components)
*/
public double totalLoss() {
double sum = 0.0;
for (double d : losses) {
sum += d;
}
return sum;
}
public Loss copy() {
return new Loss(lossNames, losses);
}
public static Loss sum(List<Loss> losses) {
if (losses.isEmpty())
return new Loss(Collections.<String>emptyList(), new double[0]);
double[] lossValues = new double[losses.get(0).losses.length];
List<String> lossNames = new ArrayList<>(losses.get(0).lossNames);
for (int i = 0; i < losses.size(); i++) {
Loss l = losses.get(i);
Preconditions.checkState(l.losses.length == lossValues.length,
"Loss %s has %s losses, the others before it had %s.", i, l.losses.length, lossValues.length);
Preconditions.checkState(l.lossNames.equals(lossNames),
"Loss %s has different loss names from the others before it. Expected %s, got %s.",
i, lossNames, l.lossNames);
for (int j = 0; j < lossValues.length; j++)
lossValues[j] += l.losses[j];
}
return new Loss(lossNames, lossValues);
}
public static Loss average(List<Loss> losses) {
Loss sum = sum(losses);
for (int i = 0; i < sum.losses.length; i++) {
sum.losses[i] /= losses.size();
}
return sum;
}
public static Loss add(Loss a, Loss b) {
Preconditions.checkState(a.lossNames.equals(b.lossNames),
"Loss names differ. First loss has names %s, second has names %s.",
a.lossNames, b.lossNames);
double[] lossValues = new double[a.losses.length];
for (int i = 0; i < lossValues.length; i++)
lossValues[i] = a.losses[i] + b.losses[i];
return new Loss(a.lossNames, lossValues);
}
public static Loss sub(Loss a, Loss b) {
Preconditions.checkState(a.lossNames.equals(b.lossNames),
"Loss names differ. First loss has names %s, second has names %s.",
a.lossNames, b.lossNames);
double[] lossValues = new double[a.losses.length];
for (int i = 0; i < lossValues.length; i++)
lossValues[i] = a.losses[i] - b.losses[i];
return new Loss(a.lossNames, lossValues);
}
public static Loss div(Loss a, Number b) {
double[] lossValues = new double[a.losses.length];
for (int i = 0; i < lossValues.length; i++)
lossValues[i] = a.losses[i] / b.doubleValue();
return new Loss(a.lossNames, lossValues);
}
public Loss add(Loss other) {
return add(this, other);
}
public Loss sub(Loss other) {
return sub(this, other);
}
public Loss plus(Loss other) {
return add(this, other);
}
public Loss minus(Loss other) {
return sub(this, other);
}
public Loss div(Number other) {
return div(this, other);
}
}
@@ -0,0 +1,55 @@
/*
* ******************************************************************************
* *
* *
* * This program and the accompanying materials are made available under the
* * terms of the Apache License, Version 2.0 which is available at
* * https://www.apache.org/licenses/LICENSE-2.0.
* *
* * See the NOTICE file distributed with this work for additional
* * information regarding copyright ownership.
* * Unless required by applicable law or agreed to in writing, software
* * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* * License for the specific language governing permissions and limitations
* * under the License.
* *
* * SPDX-License-Identifier: Apache-2.0
* *****************************************************************************
*/
package org.nd4j.autodiff.listeners;
import java.util.Map;
import org.nd4j.autodiff.samediff.SDVariable;
import org.nd4j.autodiff.samediff.SameDiff;
public enum Operation {
/**
* The training operation: {@link SameDiff#fit()} methods training step (everything except validation).
*/
TRAINING,
/**
* The training validation operation: the validation step during {@link SameDiff#fit()} methods.
*/
TRAINING_VALIDATION,
/**
* Inference operations: {@link SameDiff#output()}, {@link SameDiff#batchOutput()} and {@link SameDiff#exec(Map, String...)} ()} methods,
* as well as {@link SameDiff#execBackwards(Map, Operation, String...)} methods.
*/
INFERENCE,
/**
* Evaluation operations: {@link SameDiff#evaluate()} methods.
*/
EVALUATION;
public boolean isTrainingPhase() {
return this == TRAINING || this == TRAINING_VALIDATION;
}
public boolean isValidation() {
return this == TRAINING_VALIDATION;
}
}
@@ -0,0 +1,60 @@
/*
* ******************************************************************************
* *
* *
* * This program and the accompanying materials are made available under the
* * terms of the Apache License, Version 2.0 which is available at
* * https://www.apache.org/licenses/LICENSE-2.0.
* *
* * See the NOTICE file distributed with this work for additional
* * information regarding copyright ownership.
* * Unless required by applicable law or agreed to in writing, software
* * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* * License for the specific language governing permissions and limitations
* * under the License.
* *
* * SPDX-License-Identifier: Apache-2.0
* *****************************************************************************
*/
package org.nd4j.autodiff.listeners.checkpoint;
import lombok.AllArgsConstructor;
import lombok.Data;
import java.io.Serializable;
import java.util.Arrays;
@AllArgsConstructor
@Data
public class Checkpoint implements Serializable {
private int checkpointNum;
private long timestamp;
private int iteration;
private int epoch;
private String filename;
public static String getFileHeader(){
return "checkpointNum,timestamp,iteration,epoch,filename";
}
public static Checkpoint fromFileString(String str){
String[] split = str.split(",");
if(split.length != 5){
throw new IllegalStateException("Cannot parse checkpoint entry: expected 5 entries, got " + split.length
+ " - values = " + Arrays.toString(split));
}
return new Checkpoint(
Integer.parseInt(split[0]),
Long.parseLong(split[1]),
Integer.parseInt(split[2]),
Integer.parseInt(split[3]),
split[4]);
}
public String toFileString(){
return checkpointNum + "," + timestamp + "," + iteration + "," + epoch + "," + filename;
}
}
@@ -0,0 +1,604 @@
/*
* ******************************************************************************
* *
* *
* * This program and the accompanying materials are made available under the
* * terms of the Apache License, Version 2.0 which is available at
* * https://www.apache.org/licenses/LICENSE-2.0.
* *
* * See the NOTICE file distributed with this work for additional
* * information regarding copyright ownership.
* * Unless required by applicable law or agreed to in writing, software
* * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* * License for the specific language governing permissions and limitations
* * under the License.
* *
* * SPDX-License-Identifier: Apache-2.0
* *****************************************************************************
*/
package org.nd4j.autodiff.listeners.checkpoint;
import org.nd4j.shade.guava.io.Files;
import lombok.NonNull;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.IOUtils;
import org.nd4j.autodiff.listeners.At;
import org.nd4j.autodiff.listeners.BaseListener;
import org.nd4j.autodiff.listeners.ListenerResponse;
import org.nd4j.autodiff.listeners.Loss;
import org.nd4j.autodiff.listeners.records.LossCurve;
import org.nd4j.autodiff.listeners.Operation;
import org.nd4j.autodiff.samediff.SameDiff;
import org.nd4j.common.base.Preconditions;
import org.nd4j.linalg.dataset.api.MultiDataSet;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.concurrent.TimeUnit;
@Slf4j
public class CheckpointListener extends BaseListener implements Serializable {
private enum KeepMode {ALL, LAST, LAST_AND_EVERY};
private File rootDir;
private String fileNamePrefix;
private KeepMode keepMode;
private int keepLast;
private int keepEvery;
private boolean logSaving;
private boolean deleteExisting;
private boolean saveUpdaterState;
private Integer saveEveryNEpochs;
private Integer saveEveryNIterations;
private boolean saveEveryNIterSinceLast;
private Long saveEveryAmount;
private TimeUnit saveEveryUnit;
private Long saveEveryMs;
private boolean saveEverySinceLast;
private int lastCheckpointNum = -1;
private File checkpointRecordFile;
private Checkpoint lastCheckpoint;
private long startTime = -1;
private int startIter = -1;
private Long lastSaveEveryMsNoSinceLast;
private CheckpointListener(Builder builder){
this.rootDir = builder.rootDir;
this.fileNamePrefix = builder.fileNamePrefix;
this.keepMode = builder.keepMode;
this.keepLast = builder.keepLast;
this.keepEvery = builder.keepEvery;
this.logSaving = builder.logSaving;
this.deleteExisting = builder.deleteExisting;
this.saveUpdaterState = builder.saveUpdaterState;
this.saveEveryNEpochs = builder.saveEveryNEpochs;
this.saveEveryNIterations = builder.saveEveryNIterations;
this.saveEveryNIterSinceLast = builder.saveEveryNIterSinceLast;
this.saveEveryAmount = builder.saveEveryAmount;
this.saveEveryUnit = builder.saveEveryUnit;
this.saveEverySinceLast = builder.saveEverySinceLast;
if(saveEveryAmount != null){
saveEveryMs = TimeUnit.MILLISECONDS.convert(saveEveryAmount, saveEveryUnit);
}
if(!rootDir.exists()){
rootDir.mkdir();
}
this.checkpointRecordFile = new File(rootDir, "checkpointInfo.txt");
if(this.checkpointRecordFile.exists() && this.checkpointRecordFile.length() > 0){
if(deleteExisting){
//Delete any files matching:
//"checkpoint_" + checkpointNum + "_" + modelType + ".zip";
this.checkpointRecordFile.delete();
File[] files = rootDir.listFiles();
if(files != null && files.length > 0){
for(File f : files){
String name = f.getName();
if(name.startsWith("checkpoint_") && (name.endsWith("MultiLayerNetwork.zip") || name.endsWith("ComputationGraph.zip"))){
f.delete();
}
}
}
} else {
throw new IllegalStateException("Detected existing checkpoint files at directory " + rootDir.getAbsolutePath() +
". Use deleteExisting(true) to delete existing checkpoint files when present.");
}
}
}
@Override
public ListenerResponse epochEnd(SameDiff sameDiff, At at, LossCurve lossCurve, long epochTimeMillis) {
if(saveEveryNEpochs != null && (at.epoch()+1) % saveEveryNEpochs == 0){
//Save:
saveCheckpoint(sameDiff, at);
}
//General saving conditions: don't need to check here - will check in iterationDone
return ListenerResponse.CONTINUE;
}
@Override
public boolean isActive(Operation operation) {
return operation == Operation.TRAINING;
}
@Override
public void iterationDone(SameDiff sd, At at, MultiDataSet dataSet, Loss loss) {
if (startTime < 0) {
startTime = System.currentTimeMillis();
startIter = at.iteration();
return;
}
//Check iterations saving condition:
if(saveEveryNIterations != null){
if(saveEveryNIterSinceLast){
//Consider last saved model when deciding whether to save
long lastSaveIter = (lastCheckpoint != null ? lastCheckpoint.getIteration() : startIter);
if(at.iteration() - lastSaveIter >= saveEveryNIterations){
saveCheckpoint(sd, at);
return;
}
} else {
//Same every N iterations, regardless of saving time
if((at.iteration()+1) % saveEveryNIterations == 0){
saveCheckpoint(sd, at);
return;
}
}
}
//Check time saving condition:
long time = System.currentTimeMillis();
if(saveEveryUnit != null){
if(saveEverySinceLast){
//Consider last saved when deciding whether to save
long lastSaveTime = (lastCheckpoint != null ? lastCheckpoint.getTimestamp() : startTime);
if((time - lastSaveTime) >= saveEveryMs){
saveCheckpoint(sd, at);
return;
}
} else {
//Save periodically, regardless of when last model was saved
long lastSave = (lastSaveEveryMsNoSinceLast != null ? lastSaveEveryMsNoSinceLast : startTime);
if((time - lastSave) > saveEveryMs){
saveCheckpoint(sd, at);
lastSaveEveryMsNoSinceLast = time;
return;
}
}
}
}
private void saveCheckpoint(SameDiff sd, At at) {
try{
saveCheckpointHelper(sd, at);
} catch (Exception e){
throw new RuntimeException("Error saving checkpoint", e);
}
}
private void saveCheckpointHelper(SameDiff model, At at) throws Exception {
if(!checkpointRecordFile.exists()){
checkpointRecordFile.createNewFile();
writeCheckpointInfo(Checkpoint.getFileHeader() + "\n", checkpointRecordFile);
}
Checkpoint c = new Checkpoint(++lastCheckpointNum, System.currentTimeMillis(), at.iteration(), at.epoch(),null);
String filename = getFileName(lastCheckpointNum, at, c.getTimestamp());
c.setFilename(filename);
File saveFile = new File(rootDir, c.getFilename());
model.save(saveFile, this.saveUpdaterState);
String s = c.toFileString();
writeCheckpointInfo(s + "\n", checkpointRecordFile);
if(logSaving){
log.info("Model checkpoint saved: epoch {}, iteration {}, path: {}", c.getEpoch(), c.getIteration(),
new File(rootDir, c.getFilename()).getPath() );
}
this.lastCheckpoint = c;
//Finally: determine if we should delete some old models...
if(keepMode == null || keepMode == KeepMode.ALL){
return;
} else if(keepMode == KeepMode.LAST){
List<Checkpoint> checkpoints = availableCheckpoints();
Iterator<Checkpoint> iter = checkpoints.iterator();
while(checkpoints.size() > keepLast){
Checkpoint toRemove = iter.next();
File f = getFileForCheckpoint(toRemove);
f.delete();
iter.remove();
}
} else {
//Keep mode: last N and every M
for(Checkpoint cp : availableCheckpoints()){
if(cp.getCheckpointNum() > 0 && (cp.getCheckpointNum()+1) % keepEvery == 0){
//One of the "every M to keep" models
continue;
} else if(cp.getCheckpointNum() > lastCheckpointNum - keepLast ){ //Example: latest is 5, keep last 2 -> keep checkpoints 4 and 5
//One of last N to keep
continue;
}
//Otherwise: delete file
File f = getFileForCheckpoint(cp);
f.delete();
}
}
}
//Filename format: "<prefix>_checkpoint-#_epoch-#_iter-#_YYYY-MM-dd_HH-MM-ss.bin"
private String getFileName(int checkpointNum, At at, long time){
StringBuilder sb = new StringBuilder();
if(fileNamePrefix != null){
sb.append(fileNamePrefix);
if(!fileNamePrefix.endsWith("_")){
sb.append("_");
}
}
sb.append("checkpoint-")
.append(checkpointNum)
.append("_epoch-").append(at.epoch())
.append("_iter-").append(at.iteration());
SimpleDateFormat sdf = new SimpleDateFormat("YYYY-MM-dd_HH-mm-ss");
String date = sdf.format(new Date(time));
sb.append("_").append(date)
.append(".bin");
return sb.toString();
}
private static String writeCheckpointInfo(String str, File f){
try {
if(!f.exists()){
f.createNewFile();
}
Files.append(str, f, StandardCharsets.UTF_8);
} catch (IOException e){
throw new RuntimeException(e);
}
return str;
}
/**
* List all available checkpoints. A checkpoint is 'available' if the file can be loaded. Any checkpoint files that
* have been automatically deleted (given the configuration) will not be returned here.
*
* @return List of checkpoint files that can be loaded
*/
public List<Checkpoint> availableCheckpoints(){
if(!checkpointRecordFile.exists()){
return Collections.emptyList();
}
return availableCheckpoints(rootDir);
}
/**
* List all available checkpoints. A checkpoint is 'available' if the file can be loaded. Any checkpoint files that
* have been automatically deleted (given the configuration) will not be returned here.
* Note that the checkpointInfo.txt file must exist, as this stores checkpoint information
*
* @return List of checkpoint files that can be loaded from the specified directory
*/
public static List<Checkpoint> availableCheckpoints(File directory){
File checkpointRecordFile = new File(directory, "checkpointInfo.txt");
Preconditions.checkState(checkpointRecordFile.exists(), "Could not find checkpoint record file at expected path %s", checkpointRecordFile.getAbsolutePath());
List<String> lines;
try(InputStream is = new BufferedInputStream(new FileInputStream(checkpointRecordFile))){
lines = IOUtils.readLines(is);
} catch (IOException e){
throw new RuntimeException("Error loading checkpoint data from file: " + checkpointRecordFile.getAbsolutePath(), e);
}
List<Checkpoint> out = new ArrayList<>(lines.size()-1); //Assume first line is header
for( int i=1; i<lines.size(); i++ ){
Checkpoint c = Checkpoint.fromFileString(lines.get(i));
if(new File(directory, c.getFilename()).exists()){
out.add(c);
}
}
return out;
}
/**
* Return the most recent checkpoint, if one exists - otherwise returns null
* @return Checkpoint
*/
public Checkpoint lastCheckpoint(){
if(!checkpointRecordFile.exists()){
return null;
}
return lastCheckpoint(rootDir);
}
/**
* Return the most recent checkpoint, if one exists - otherwise returns null
* @param rootDir Root direcotry for the checkpoint files
* @return Checkpoint
*/
public static Checkpoint lastCheckpoint(File rootDir){
List<Checkpoint> all = availableCheckpoints(rootDir);
if(all.isEmpty()){
return null;
}
return all.get(all.size()-1);
}
/**
* Get the model file for the given checkpoint. Checkpoint model file must exist
*
* @param checkpoint Checkpoint to get the model file for
* @return Model file for the checkpoint
*/
public File getFileForCheckpoint(Checkpoint checkpoint){
return getFileForCheckpoint(checkpoint.getCheckpointNum());
}
/**
* Get the model file for the given checkpoint number. Checkpoint model file must exist
*
* @param checkpointNum Checkpoint number to get the model file for
* @return Model file for the checkpoint
*/
public File getFileForCheckpoint(int checkpointNum) {
return getFileForCheckpoint(rootDir, checkpointNum);
}
public static File getFileForCheckpoint(File rootDir, int checkpointNum){
//Scan the root directory, for a file matching the checkpoint filename pattern:
//Filename format: "<prefix>_checkpoint-#_epoch-#_iter-#_YYYY-MM-dd_HH-MM-ss.bin"
if(checkpointNum < 0){
throw new IllegalArgumentException("Invalid checkpoint number: " + checkpointNum);
}
String contains = "_checkpoint-" + checkpointNum + "_epoch-";
File[] allFiles = rootDir.listFiles();
if(allFiles != null){
for(File f : allFiles){
if(f.getAbsolutePath().contains(contains)){
return f;
}
}
}
throw new IllegalStateException("Model file for checkpoint " + checkpointNum + " does not exist");
}
/**
* Load a given checkpoint number
*
* @param loadUpdaterState If true: load the updater state. See {@link SameDiff#load(File, boolean)} for more details
*
*/
public SameDiff loadCheckpoint(int checkpointNum, boolean loadUpdaterState){
return loadCheckpoint(rootDir, checkpointNum, loadUpdaterState);
}
/**
* Load a SameDiff instance for the given checkpoint that resides in the specified root directory
*
* @param rootDir Directory that the checkpoint resides in
* @param checkpointNum Checkpoint model number to load
* @param loadUpdaterState If true: load the updater state. See {@link SameDiff#load(File, boolean)} for more details
* @return The loaded model
*/
public static SameDiff loadCheckpoint(File rootDir, int checkpointNum, boolean loadUpdaterState) {
File f = getFileForCheckpoint(rootDir, checkpointNum);
return SameDiff.load(f, loadUpdaterState);
}
/**
* Load the last (most recent) checkpoint from the specified root directory
* @param rootDir Root directory to load checpoint from
* @return ComputationGraph for last checkpoint
*/
public static SameDiff loadLastCheckpoint(File rootDir, boolean loadUpdaterState){
Checkpoint last = lastCheckpoint(rootDir);
return loadCheckpoint(rootDir, last.getCheckpointNum(), loadUpdaterState);
}
public static Builder builder(@NonNull File rootDir){
return new Builder(rootDir);
}
public static class Builder {
private File rootDir;
private String fileNamePrefix = "SameDiff";
private KeepMode keepMode;
private int keepLast;
private int keepEvery;
private boolean saveUpdaterState = true;
private boolean logSaving = true;
private boolean deleteExisting = false;
private Integer saveEveryNEpochs;
private Integer saveEveryNIterations;
private boolean saveEveryNIterSinceLast;
private Long saveEveryAmount;
private TimeUnit saveEveryUnit;
private boolean saveEverySinceLast;
/**
* @param rootDir Root directory to save models to
*/
public Builder(@NonNull String rootDir){
this(new File(rootDir));
}
/**
* @param rootDir Root directory to save models to
*/
public Builder(@NonNull File rootDir){
this.rootDir = rootDir;
}
public Builder fileNamePrefix(String fileNamePrefix){
this.fileNamePrefix = fileNamePrefix;
return this;
}
/**
* Save a model at the end of every epoch
*/
public Builder saveEveryEpoch(){
return saveEveryNEpochs(1);
}
/**
* Save a model at the end of every N epochs
*/
public Builder saveEveryNEpochs(int n){
this.saveEveryNEpochs = n;
return this;
}
/**
* Save a model every N iterations
*/
public Builder saveEveryNIterations(int n){
return saveEveryNIterations(n, false);
}
/**
* Save a model every N iterations (if sinceLast == false), or if N iterations have passed since
* the last model vas saved (if sinceLast == true)
*/
public Builder saveEveryNIterations(int n, boolean sinceLast){
this.saveEveryNIterations = n;
this.saveEveryNIterSinceLast = sinceLast;
return this;
}
/**
* Save a model periodically
*
* @param amount Quantity of the specified time unit
* @param timeUnit Time unit
*/
public Builder saveEvery(long amount, TimeUnit timeUnit){
return saveEvery(amount, timeUnit, false);
}
/**
* Save a model periodically (if sinceLast == false), or if the specified amount of time has elapsed since
* the last model was saved (if sinceLast == true)
*
* @param amount Quantity of the specified time unit
* @param timeUnit Time unit
*/
public Builder saveEvery(long amount, TimeUnit timeUnit, boolean sinceLast){
this.saveEveryAmount = amount;
this.saveEveryUnit = timeUnit;
this.saveEverySinceLast = sinceLast;
return this;
}
/**
* Keep all model checkpoints - i.e., don't delete any. Note that this is the default.
*/
public Builder keepAll(){
this.keepMode = KeepMode.ALL;
return this;
}
/**
* Keep only the last N most recent model checkpoint files. Older checkpoints will automatically be deleted.
* @param n Number of most recent checkpoints to keep
*/
public Builder keepLast(int n){
if(n <= 0){
throw new IllegalArgumentException("Number of model files to keep should be > 0 (got: " + n + ")");
}
this.keepMode = KeepMode.LAST;
this.keepLast = n;
return this;
}
/**
* Keep the last N most recent model checkpoint files, <i>and</i> every M checkpoint files.<br>
* For example: suppose you save every 100 iterations, for 2050 iteration, and use keepLastAndEvery(3,5).
* This means after 2050 iterations you would have saved 20 checkpoints - some of which will be deleted.
* Those remaining in this example: iterations 500, 1000, 1500, 1800, 1900, 2000.
* @param nLast Most recent checkpoints to keep
* @param everyN Every N checkpoints to keep (regardless of age)
*/
public Builder keepLastAndEvery(int nLast, int everyN){
if(nLast <= 0){
throw new IllegalArgumentException("Most recent number of model files to keep should be > 0 (got: "
+ nLast + ")");
}
if(everyN <= 0){
throw new IllegalArgumentException("Every n model files to keep should be > 0 (got: "
+ everyN + ")");
}
this.keepMode = KeepMode.LAST_AND_EVERY;
this.keepLast = nLast;
this.keepEvery = everyN;
return this;
}
/**
* If true (the default) log a message every time a model is saved
*
* @param logSaving Whether checkpoint saves should be logged or not
*/
public Builder logSaving(boolean logSaving){
this.logSaving = logSaving;
return this;
}
/**
* Whether the updater state (history/state for Adam, Nesterov momentum, etc) should be saved with each checkpoint.<br>
* Updater state is saved by default.
* If you expect to continue training on any of the checkpoints, this should be set to true. However, it will increase
* the file size.
* @param saveUpdaterState If true: updater state will be saved with checkpoints. False: not saved.
*/
public Builder saveUpdaterState(boolean saveUpdaterState){
this.saveUpdaterState = saveUpdaterState;
return this;
}
/**
* If the checkpoint listener is set to save to a non-empty directory, should the CheckpointListener-related
* content be deleted?<br>
* This is disabled by default (and instead, an exception will be thrown if existing data is found)<br>
* WARNING: Be careful when enabling this, as it deletes all saved checkpoint models in the specified directory!
*/
public Builder deleteExisting(boolean deleteExisting){
this.deleteExisting = deleteExisting;
return this;
}
public CheckpointListener build(){
if(saveEveryNEpochs == null && saveEveryAmount == null && saveEveryNIterations == null){
throw new IllegalStateException("Cannot construct listener: no models will be saved (must use at least" +
" one of: save every N epochs, every N iterations, or every T time periods)");
}
return new CheckpointListener(this);
}
}
}
@@ -0,0 +1,139 @@
/*
* ******************************************************************************
* *
* *
* * This program and the accompanying materials are made available under the
* * terms of the Apache License, Version 2.0 which is available at
* * https://www.apache.org/licenses/LICENSE-2.0.
* *
* * See the NOTICE file distributed with this work for additional
* * information regarding copyright ownership.
* * Unless required by applicable law or agreed to in writing, software
* * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* * License for the specific language governing permissions and limitations
* * under the License.
* *
* * SPDX-License-Identifier: Apache-2.0
* *****************************************************************************
*/
package org.nd4j.autodiff.listeners.debugging;
import lombok.NonNull;
import org.nd4j.autodiff.listeners.At;
import org.nd4j.autodiff.listeners.BaseListener;
import org.nd4j.autodiff.listeners.Operation;
import org.nd4j.autodiff.samediff.SameDiff;
import org.nd4j.autodiff.samediff.internal.SameDiffOp;
import org.nd4j.common.base.Preconditions;
import org.nd4j.linalg.api.buffer.DataType;
import org.nd4j.linalg.api.ndarray.INDArray;
import org.nd4j.linalg.api.ops.OpContext;
import org.nd4j.linalg.api.ops.impl.transforms.pairwise.bool.Xor;
import org.nd4j.linalg.dataset.api.MultiDataSet;
import org.nd4j.linalg.factory.Nd4j;
import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class ArraySavingListener extends BaseListener {
protected final File dir;
protected int count = 0;
public ArraySavingListener(@NonNull File dir){
if(!dir.exists()){
dir.mkdir();
}
if(dir.listFiles() != null && dir.listFiles().length > 0){
throw new IllegalStateException("Directory is not empty: " + dir.getAbsolutePath());
}
this.dir = dir;
}
@Override
public boolean isActive(Operation operation) {
return true;
}
@Override
public void opExecution(SameDiff sd, At at, MultiDataSet batch, SameDiffOp op, OpContext opContext, INDArray[] outputs) {
List<String> outNames = op.getOutputsOfOp();
for(int i=0; i<outputs.length; i++ ){
String filename = (count++) + "_" + outNames.get(i).replaceAll("/", "__") + ".bin";
File outFile = new File(dir, filename);
INDArray arr = outputs[i];
try {
Nd4j.saveBinary(arr, outFile);
System.out.println(outFile.getAbsolutePath());
} catch (IOException e){
throw new RuntimeException(e);
}
}
}
public static void compare(File dir1, File dir2, double eps) throws Exception {
File[] files1 = dir1.listFiles();
File[] files2 = dir2.listFiles();
Preconditions.checkNotNull(files1, "No files in directory 1: %s", dir1);
Preconditions.checkNotNull(files2, "No files in directory 2: %s", dir2);
Preconditions.checkState(files1.length == files2.length, "Different number of files: %s vs %s", files1.length, files2.length);
Map<String,File> m1 = toMap(files1);
Map<String,File> m2 = toMap(files2);
for(File f : files1){
String name = f.getName();
String varName = name.substring(name.indexOf('_') + 1, name.length()-4); //Strip "x_" and ".bin"
File f2 = m2.get(varName);
INDArray arr1 = Nd4j.readBinary(f);
INDArray arr2 = Nd4j.readBinary(f2);
//TODO String arrays won't work here!
boolean eq = arr1.equalsWithEps(arr2, eps);
if(eq){
System.out.println("Equals: " + varName.replaceAll("__", "/"));
} else {
if(arr1.dataType() == DataType.BOOL){
INDArray xor = Nd4j.exec(new Xor(arr1, arr2));
int count = xor.castTo(DataType.INT).sumNumber().intValue();
System.out.println("FAILS: " + varName.replaceAll("__", "/") + " - boolean, # differences = " + count);
System.out.println("\t" + f.getAbsolutePath());
System.out.println("\t" + f2.getAbsolutePath());
xor.close();
} else {
INDArray sub = arr1.sub(arr2);
INDArray diff = Nd4j.math.abs(sub);
double maxDiff = diff.maxNumber().doubleValue();
System.out.println("FAILS: " + varName.replaceAll("__", "/") + " - max difference = " + maxDiff);
System.out.println("\t" + f.getAbsolutePath());
System.out.println("\t" + f2.getAbsolutePath());
sub.close();
diff.close();
}
}
arr1.close();
arr2.close();
}
}
private static Map<String,File> toMap(File[] files){
Map<String,File> ret = new HashMap<>();
for(File f : files) {
String name = f.getName();
String varName = name.substring(name.indexOf('_') + 1, name.length() - 4); //Strip "x_" and ".bin"
ret.put(varName, f);
}
return ret;
}
}
@@ -0,0 +1,86 @@
/*
* ******************************************************************************
* *
* *
* * This program and the accompanying materials are made available under the
* * terms of the Apache License, Version 2.0 which is available at
* * https://www.apache.org/licenses/LICENSE-2.0.
* *
* * See the NOTICE file distributed with this work for additional
* * information regarding copyright ownership.
* * Unless required by applicable law or agreed to in writing, software
* * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* * License for the specific language governing permissions and limitations
* * under the License.
* *
* * SPDX-License-Identifier: Apache-2.0
* *****************************************************************************
*/
package org.nd4j.autodiff.listeners.debugging;
import org.nd4j.autodiff.listeners.At;
import org.nd4j.autodiff.listeners.BaseListener;
import org.nd4j.autodiff.listeners.Operation;
import org.nd4j.autodiff.samediff.SameDiff;
import org.nd4j.autodiff.samediff.internal.SameDiffOp;
import org.nd4j.common.primitives.Counter;
import org.nd4j.linalg.api.ndarray.INDArray;
import org.nd4j.linalg.api.ops.OpContext;
import org.nd4j.linalg.api.ops.impl.controlflow.compat.*;
import org.nd4j.linalg.dataset.api.MultiDataSet;
import java.util.ArrayList;
import java.util.List;
public class ControlflowListener extends BaseListener {
private Counter<String> entersExecuted = new Counter<>();
private Counter<String> exitsExecuted = new Counter<>();
private Counter<String> mergesExecuted = new Counter<>();
private Counter<String> nextIterationExecuted = new Counter<>();
private Counter<String> switchesExecuted = new Counter<>();
private Counter<String> loopCondExecuted = new Counter<>();
@Override
public boolean isActive(Operation operation) {
return true;
}
@Override
public void operationStart(SameDiff sd, Operation op) {
super.operationStart(sd, op);
}
@Override
public void operationEnd(SameDiff sd, Operation op) {
super.operationEnd(sd, op);
}
@Override
public void preOpExecution(SameDiff sd, At at, SameDiffOp op, OpContext opContext) {
super.preOpExecution(sd, at, op, opContext);
}
@Override
public void opExecution(SameDiff sd, At at, MultiDataSet batch, SameDiffOp op, OpContext opContext, INDArray[] outputs) {
super.opExecution(sd, at, batch, op, opContext, outputs);
if(op.getOp() instanceof Enter) {
entersExecuted.incrementCount(op.getName(),1.0);
} else if(op.getOp() instanceof Exit) {
exitsExecuted.incrementCount(op.getName(),1.0);
} else if(op.getOp() instanceof NextIteration) {
nextIterationExecuted.incrementCount(op.getName(),1.0);
} else if(op.getOp() instanceof Switch) {
switchesExecuted.incrementCount(op.getName(),1.0);
} else if(op.getOp() instanceof Merge) {
mergesExecuted.incrementCount(op.getName(),1.0);
} else if(op.getOp() instanceof LoopCond) {
loopCondExecuted.incrementCount(op.getName(),1.0);
}
}
}
@@ -0,0 +1,252 @@
/*
* ******************************************************************************
* *
* *
* * This program and the accompanying materials are made available under the
* * terms of the Apache License, Version 2.0 which is available at
* * https://www.apache.org/licenses/LICENSE-2.0.
* *
* * See the NOTICE file distributed with this work for additional
* * information regarding copyright ownership.
* * Unless required by applicable law or agreed to in writing, software
* * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* * License for the specific language governing permissions and limitations
* * under the License.
* *
* * SPDX-License-Identifier: Apache-2.0
* *****************************************************************************
*/
package org.nd4j.autodiff.listeners.debugging;
import lombok.val;
import org.nd4j.autodiff.functions.DifferentialFunction;
import org.nd4j.autodiff.listeners.At;
import org.nd4j.autodiff.listeners.BaseListener;
import org.nd4j.autodiff.listeners.Operation;
import org.nd4j.autodiff.samediff.SameDiff;
import org.nd4j.autodiff.samediff.internal.SameDiffOp;
import org.nd4j.linalg.api.buffer.DataType;
import org.nd4j.linalg.api.ndarray.INDArray;
import org.nd4j.linalg.api.ops.CustomOp;
import org.nd4j.linalg.api.ops.Op;
import org.nd4j.linalg.api.ops.OpContext;
import org.nd4j.linalg.api.ops.ScalarOp;
import java.util.Arrays;
public class ExecDebuggingListener extends BaseListener {
public enum PrintMode {OPS_ONLY, SHAPES_ONLY, REPRODUCE}
private final PrintMode printMode;
private final int maxIterations;
private final boolean logIter;
private long printIterations = 0;
private int lastIter = -1;
private int stepThisIter = 0;
/**
* @param printMode Print mode, see {@link PrintMode}
* @param maxIterations Maximum number of iterations to print. <= 0 for "all iterations"
* @param logIter If true: prefix iteration/epoch, such as "(iter=1,epoch=0,op=3)" to the output
*/
public ExecDebuggingListener(PrintMode printMode, int maxIterations, boolean logIter){
this.printMode = printMode;
this.maxIterations = maxIterations;
this.logIter = logIter;
}
@Override
public boolean isActive(Operation operation) {
return true;
}
@Override
public void preOpExecution(SameDiff sd, At at, SameDiffOp op, OpContext opContext) {
if(lastIter != at.iteration()){
lastIter = at.iteration();
stepThisIter = 0;
printIterations++;
}
if(maxIterations > 0 && printIterations > maxIterations){
return;
}
StringBuilder sb = new StringBuilder();
if(logIter){
sb.append("(iter=").append(at.iteration())
.append(",epoch=").append(at.epoch())
.append(",");
}
sb.append("op=").append(stepThisIter++)
.append(logIter ? ") " : " - ");
DifferentialFunction df = op.getOp();
sb.append(op.getOp().getClass().getName());
CustomOp co = df instanceof CustomOp ? (CustomOp) df : null;
Op lOp = df instanceof Op ? (Op) df : null;
if(printMode == PrintMode.OPS_ONLY){
sb.append("\n");
} else if(printMode == PrintMode.SHAPES_ONLY){
if(co != null){
if(co.iArgs() != null && co.iArgs().length > 0) {
sb.append("\n\tiArgs=").append(Arrays.toString(co.iArgs()));
}
if(co.bArgs() != null && co.bArgs().length > 0) {
sb.append("\n\tbArgs=").append(Arrays.toString(co.bArgs()));
}
if(co.tArgs() != null && co.tArgs().length > 0) {
sb.append("\n\ttArgs=").append(Arrays.toString(co.tArgs()));
}
val inputs = co.inputArguments();
val outputs = co.outputArguments();
if(inputs != null ) {
for (int i = 0; i < inputs.size(); i++) {
sb.append("\n\tInput[").append(i).append("]=").append(inputs.get(i).shapeInfoToString());
}
}
if(outputs != null ) {
for (int i = 0; i < outputs.size(); i++) {
sb.append("\n\tOutputs[").append(i).append("]=").append(outputs.get(i).shapeInfoToString());
}
}
} else {
if(lOp.x() != null) {
sb.append("\n\tx: ").append(lOp.x().shapeInfoToString());
}
if(lOp.y() != null) {
sb.append("\n\ty: ").append(lOp.y().shapeInfoToString());
}
if(lOp.z() != null) {
sb.append("\n\tz: ").append(lOp.z().shapeInfoToString());
}
if(lOp instanceof ScalarOp){
INDArray scalar = ((ScalarOp)lOp).scalar();
if(scalar != null){
sb.append("\n\tscalar: ").append(scalar.shapeInfoToString());
}
}
}
sb.append("\n");
} else if(printMode == PrintMode.REPRODUCE){
sb.append("\n");
if(co != null){
sb.append("DynamicCustomOp op = new ").append(co.getClass().getName()).append("();\n");
if(co.iArgs() != null && co.iArgs().length > 0 ){
sb.append("op.addIArgument(").append(Arrays.toString(co.iArgs()).replaceAll("[\\[\\]]", "")).append(");\n");
}
if(co.bArgs() != null && co.bArgs().length > 0 ){
sb.append("op.addBArgument(").append(Arrays.toString(co.bArgs()).replaceAll("[\\[\\]]", "")).append(");\n");
}
if(co.tArgs() != null && co.tArgs().length > 0 ){
sb.append("op.addTArgument(").append(Arrays.toString(co.tArgs()).replaceAll("[\\[\\]]", "")).append(");\n");
}
val inputs = co.inputArguments();
val outputs = co.outputArguments();
if(inputs != null ) {
sb.append("INDArray[] inputs = new INDArray[").append(inputs.size()).append("];\n");
for (int i = 0; i < inputs.size(); i++) {
sb.append("inputs[").append(i).append("] = ");
sb.append(createString(inputs.get(i)))
.append(";\n");
}
sb.append("op.addInputArgument(inputs);\n");
}
if(outputs != null ) {
sb.append("INDArray[] outputs = new INDArray[").append(outputs.size()).append("];\n");
for (int i = 0; i < outputs.size(); i++) {
sb.append("outputs[").append(i).append("] = ");
sb.append(createString(outputs.get(i)))
.append(";\n");
}
sb.append("op.addOutputArgument(outputs);\n");
}
} else {
sb.append("Op op = new ").append(op.getClass().getName()).append("();\n");
if(lOp.x() != null) {
sb.append("op.setX(").append(createString(lOp.x())).append(");\n");
}
if(lOp.y() != null) {
sb.append("op.setY(").append(createString(lOp.y())).append(");\n");
}
if(lOp.z() != null) {
sb.append("op.setZ").append(createString(lOp.z())).append(");\n");
}
if(lOp instanceof ScalarOp){
INDArray scalar = ((ScalarOp)lOp).scalar();
if(scalar != null){
sb.append("((ScalarOp)op).setScalar(").append(createString(scalar)).append(");\n");
}
}
}
sb.append("Nd4j.exec(op);\n");
}
System.out.print(sb);
}
private static String createString(INDArray arr) {
StringBuilder sb = new StringBuilder();
if(arr.isEmpty()){
sb.append("Nd4j.empty(DataType.").append(arr.dataType()).append(");");
} else {
sb.append("Nd4j.createFromArray(");
DataType dt = arr.dataType();
switch (dt){
case DOUBLE:
double[] dArr = arr.dup().data().asDouble();
sb.append(Arrays.toString(dArr).replaceAll("[\\[\\]]", ""));
break;
case FLOAT:
case HALF:
case BFLOAT16:
float[] fArr = arr.dup().data().asFloat();
sb.append(Arrays.toString(fArr)
.replaceAll(",", "f,")
.replaceAll("]", "f")
.replaceAll("[\\[\\]]", ""));
break;
case LONG:
case UINT32:
case UINT64:
long[] lArr = arr.dup().data().asLong();
sb.append(Arrays.toString(lArr)
.replaceAll(",", "L,")
.replaceAll("]", "L")
.replaceAll("[\\[\\]]", ""));
break;
case INT:
case SHORT:
case UBYTE:
case BYTE:
case UINT16:
case BOOL:
int[] iArr = arr.dup().data().asInt();
sb.append(Arrays.toString(iArr).replaceAll("[\\[\\]]", ""));
break;
case UTF8:
break;
case COMPRESSED:
case UNKNOWN:
break;
}
sb.append(").reshape(").append(Arrays.toString(arr.shape()).replaceAll("[\\[\\]]", ""))
.append(")");
if(dt == DataType.HALF || dt == DataType.BFLOAT16 || dt == DataType.UINT32 || dt == DataType.UINT64 ||
dt == DataType.SHORT || dt == DataType.UBYTE || dt == DataType.BYTE || dt == DataType.UINT16 || dt == DataType.BOOL){
sb.append(".cast(DataType.").append(arr.dataType()).append(")");
}
}
return sb.toString();
}
}
@@ -0,0 +1,207 @@
/*
* ******************************************************************************
* *
* *
* * This program and the accompanying materials are made available under the
* * terms of the Apache License, Version 2.0 which is available at
* * https://www.apache.org/licenses/LICENSE-2.0.
* *
* * See the NOTICE file distributed with this work for additional
* * information regarding copyright ownership.
* * Unless required by applicable law or agreed to in writing, software
* * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* * License for the specific language governing permissions and limitations
* * under the License.
* *
* * SPDX-License-Identifier: Apache-2.0
* *****************************************************************************
*/
package org.nd4j.autodiff.listeners.debugging;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.Getter;
import lombok.NonNull;
import org.nd4j.autodiff.listeners.At;
import org.nd4j.autodiff.listeners.BaseListener;
import org.nd4j.autodiff.listeners.Operation;
import org.nd4j.autodiff.samediff.SameDiff;
import org.nd4j.autodiff.samediff.internal.SameDiffOp;
import org.nd4j.linalg.api.ndarray.INDArray;
import org.nd4j.linalg.api.ops.DynamicCustomOp;
import org.nd4j.linalg.api.ops.Op;
import org.nd4j.linalg.api.ops.OpContext;
import org.nd4j.linalg.dataset.api.MultiDataSet;
import org.nd4j.linalg.factory.Nd4j;
import org.nd4j.common.util.ArrayUtil;
import java.text.DecimalFormat;
import java.util.*;
@Getter
public class OpBenchmarkListener extends BaseListener {
public enum Mode {SINGLE_ITER_PRINT, AGGREGATE}
private final Operation operation;
private final Mode mode;
private final long minRuntime;
private Map<String,OpExec> aggregateModeMap;
@Getter(AccessLevel.PRIVATE)
private long start;
@Getter(AccessLevel.PRIVATE)
private boolean printActive;
private boolean printDone;
public OpBenchmarkListener(Operation operation, @NonNull Mode mode) {
this(operation, mode, 0);
}
/**
* @param operation Operation to collect stats for
* @param mode Mode - see {@link OpBenchmarkListener}
* @param minRuntime Minimum runtime - only applies to Mode.SINGLE_ITER_PRINT. If op runtime below this: don't print
*/
public OpBenchmarkListener(Operation operation, @NonNull Mode mode, long minRuntime) {
this.operation = operation;
this.mode = mode;
this.minRuntime = minRuntime;
}
@Override
public boolean isActive(Operation operation) {
return this.operation == null || this.operation == operation;
}
@Override
public void operationStart(SameDiff sd, Operation op) {
if(printDone)
return;
if(this.operation == null || this.operation == op)
printActive = true;
}
@Override
public void operationEnd(SameDiff sd, Operation op) {
if(printDone)
return;
if(this.operation == null || this.operation == op) {
printActive = false;
printDone = true;
}
}
@Override
public void preOpExecution(SameDiff sd, At at, SameDiffOp op, OpContext opContext) {
start = System.currentTimeMillis();
}
@Override
public void opExecution(SameDiff sd, At at, MultiDataSet batch, SameDiffOp op, OpContext opContext, INDArray[] outputs) {
long now = System.currentTimeMillis();
if (mode == Mode.SINGLE_ITER_PRINT && printActive && (now-start) > this.minRuntime) {
System.out.println(getOpString(op, now));
} else if (mode == Mode.AGGREGATE) {
if(aggregateModeMap == null)
aggregateModeMap = new LinkedHashMap<>();
if(!aggregateModeMap.containsKey(op.getName())){
String s = getOpString(op, null);
OpExec oe = new OpExec(op.getName(), op.getOp().opName(), op.getOp().getClass(),
new ArrayList<Long>(), s);
aggregateModeMap.put(op.getName(), oe);
}
aggregateModeMap.get(op.getName()).getRuntimeMs().add(now-start);
}
}
private String getOpString(SameDiffOp op, Long now){
StringBuilder sb = new StringBuilder();
sb.append(op.getName()).append(" - ").append(op.getOp().getClass().getSimpleName())
.append("(").append(op.getOp().opName()).append(") - ");
if(now != null) {
sb.append(now - start).append(" ms\n");
}
if (op.getOp() instanceof DynamicCustomOp) {
DynamicCustomOp dco = (DynamicCustomOp) op.getOp();
int x = 0;
for (INDArray i : dco.inputArguments()) {
sb.append(" in ").append(x++).append(": ").append(i.shapeInfoToString()).append("\n");
}
x = 0;
for (INDArray o : dco.outputArguments()) {
sb.append(" out ").append(x++).append(": ").append(o.shapeInfoToString()).append("\n");
}
long[] iargs = dco.iArgs();
boolean[] bargs = dco.bArgs();
double[] targs = dco.tArgs();
if (iargs != null && iargs.length > 0) {
sb.append(" iargs: ").append(Arrays.toString(iargs)).append("\n");
}
if (bargs != null && bargs.length > 0) {
sb.append(" bargs: ").append(Arrays.toString(bargs)).append("\n");
}
if (targs != null && targs.length > 0) {
sb.append(" targs: ").append(Arrays.toString(targs)).append("\n");
}
} else {
Op o = (Op) op.getOp();
if (o.x() != null)
sb.append(" x: ").append(o.x().shapeInfoToString());
if (o.y() != null)
sb.append(" y: ").append(o.y().shapeInfoToString());
if (o.z() != null)
sb.append(" z: ").append(o.z().shapeInfoToString());
}
return sb.toString();
}
@AllArgsConstructor
@Data
public static class OpExec {
private final String opOwnName;
private final String opName;
private final Class<?> opClass;
private List<Long> runtimeMs;
private String firstIter;
@Override
public String toString(){
DecimalFormat df = new DecimalFormat("0.000");
return opOwnName + " - op class: " + opClass.getSimpleName() + " (op name: " + opName + ")\n"
+ "count: " + runtimeMs.size() + ", mean: " + df.format(avgMs()) + "ms, std: " + df.format(stdMs()) + "ms, min: " + minMs() + "ms, max: " + maxMs() + "ms\n"
+ firstIter;
}
public double avgMs() {
long sum = 0;
for (Long l : runtimeMs) {
sum += l;
}
return sum / (double) runtimeMs.size();
}
public double stdMs() {
return Nd4j.createFromArray(ArrayUtil.toArrayLong(runtimeMs)).stdNumber().doubleValue();
}
public long minMs() {
return Nd4j.createFromArray(ArrayUtil.toArrayLong(runtimeMs)).minNumber().longValue();
}
public long maxMs() {
return Nd4j.createFromArray(ArrayUtil.toArrayLong(runtimeMs)).maxNumber().longValue();
}
}
}
@@ -0,0 +1,115 @@
/*
* ******************************************************************************
* *
* *
* * This program and the accompanying materials are made available under the
* * terms of the Apache License, Version 2.0 which is available at
* * https://www.apache.org/licenses/LICENSE-2.0.
* *
* * See the NOTICE file distributed with this work for additional
* * information regarding copyright ownership.
* * Unless required by applicable law or agreed to in writing, software
* * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* * License for the specific language governing permissions and limitations
* * under the License.
* *
* * SPDX-License-Identifier: Apache-2.0
* *****************************************************************************
*/
package org.nd4j.autodiff.listeners.impl;
import java.util.ArrayList;
import java.util.List;
import lombok.Getter;
import lombok.Setter;
import org.nd4j.autodiff.listeners.At;
import org.nd4j.autodiff.listeners.BaseEvaluationListener;
import org.nd4j.autodiff.listeners.records.EvaluationRecord;
import org.nd4j.autodiff.listeners.records.History;
import org.nd4j.autodiff.listeners.ListenerEvaluations;
import org.nd4j.autodiff.listeners.ListenerResponse;
import org.nd4j.autodiff.listeners.records.LossCurve;
import org.nd4j.autodiff.listeners.Operation;
import org.nd4j.autodiff.samediff.SameDiff;
import org.nd4j.autodiff.samediff.TrainingConfig;
public class HistoryListener extends BaseEvaluationListener {
@Getter
@Setter
private ListenerEvaluations evaluations;
private List<EvaluationRecord> trainingHistory = new ArrayList<>();
private List<EvaluationRecord> validationHistory = new ArrayList<>();
private LossCurve loss = null;
private long startTime;
private long endTime;
private List<Long> validationTimes = new ArrayList<>();
private long validationStartTime;
public HistoryListener(TrainingConfig tc) {
this.evaluations = new ListenerEvaluations(tc.getTrainEvaluations(), tc.getTrainEvaluationLabels(),
tc.getValidationEvaluations(), tc.getValidationEvaluationLabels());
}
public HistoryListener(ListenerEvaluations evaluations) {
this.evaluations = evaluations;
}
public HistoryListener newInstance() {
return new HistoryListener(evaluations);
}
@Override
public ListenerEvaluations evaluations() {
return evaluations;
}
@Override
public boolean isActive(Operation operation) {
return operation.isTrainingPhase();
}
@Override
public ListenerResponse epochEndEvaluations(SameDiff sd, At at, LossCurve lossCurve, long epochTimeMillis, EvaluationRecord evaluations) {
trainingHistory.add(evaluations);
loss = lossCurve;
return ListenerResponse.CONTINUE;
}
@Override
public ListenerResponse validationDoneEvaluations(SameDiff sd, At at, long validationTimeMillis, EvaluationRecord evaluations) {
validationHistory.add(evaluations);
return ListenerResponse.CONTINUE;
}
@Override
public void operationStart(SameDiff sd, Operation op) {
if (op == Operation.TRAINING) {
startTime = System.currentTimeMillis();
} else if (op == Operation.TRAINING_VALIDATION) {
validationStartTime = System.currentTimeMillis();
}
}
@Override
public void operationEnd(SameDiff sd, Operation op) {
if (op == Operation.TRAINING) {
endTime = System.currentTimeMillis();
} else if (op == Operation.TRAINING_VALIDATION) {
validationTimes.add(System.currentTimeMillis() - validationStartTime);
}
}
public History getReport() {
return new History(trainingHistory, validationHistory, loss, endTime - startTime, validationTimes);
}
}
@@ -0,0 +1,222 @@
/*
* ******************************************************************************
* *
* *
* * This program and the accompanying materials are made available under the
* * terms of the Apache License, Version 2.0 which is available at
* * https://www.apache.org/licenses/LICENSE-2.0.
* *
* * See the NOTICE file distributed with this work for additional
* * information regarding copyright ownership.
* * Unless required by applicable law or agreed to in writing, software
* * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* * License for the specific language governing permissions and limitations
* * under the License.
* *
* * SPDX-License-Identifier: Apache-2.0
* *****************************************************************************
*/
package org.nd4j.autodiff.listeners.impl;
import lombok.extern.slf4j.Slf4j;
import org.nd4j.autodiff.listeners.At;
import org.nd4j.autodiff.listeners.BaseListener;
import org.nd4j.autodiff.listeners.ListenerResponse;
import org.nd4j.autodiff.listeners.Loss;
import org.nd4j.autodiff.listeners.records.LossCurve;
import org.nd4j.autodiff.listeners.Operation;
import org.nd4j.autodiff.samediff.SameDiff;
import org.nd4j.common.base.Preconditions;
import org.nd4j.linalg.dataset.api.MultiDataSet;
import java.text.DecimalFormat;
@Slf4j
public class ScoreListener extends BaseListener {
private final int frequency;
private final boolean reportEpochs;
private final boolean reportIterPerformance;
private long epochExampleCount;
private int epochBatchCount;
private long etlTotalTimeEpoch;
private long lastIterTime;
private long etlTimeSumSinceLastReport;
private long iterTimeSumSinceLastReport;
private int examplesSinceLastReportIter;
private long lastReportTime = -1;
/**
* Create a ScoreListener reporting every 10 iterations, and at the end of each epoch
*/
public ScoreListener() {
this(10, true);
}
/**
* Create a ScoreListener reporting every N iterations, and at the end of each epoch
*/
public ScoreListener(int frequency) {
this(frequency, true);
}
/**
* Create a ScoreListener reporting every N iterations, and optionally at the end of each epoch
*/
public ScoreListener(int frequency, boolean reportEpochs) {
this(frequency, reportEpochs, true);
}
public ScoreListener(int frequency, boolean reportEpochs, boolean reportIterPerformance) {
Preconditions.checkArgument(frequency > 0, "ScoreListener frequency must be > 0, got %s", frequency);
this.frequency = frequency;
this.reportEpochs = reportEpochs;
this.reportIterPerformance = reportIterPerformance;
}
@Override
public boolean isActive(Operation operation) {
return operation == Operation.TRAINING;
}
@Override
public void epochStart(SameDiff sd, At at) {
if (reportEpochs) {
epochExampleCount = 0;
epochBatchCount = 0;
etlTotalTimeEpoch = 0;
}
lastReportTime = -1;
examplesSinceLastReportIter = 0;
}
@Override
public ListenerResponse epochEnd(SameDiff sd, At at, LossCurve lossCurve, long epochTimeMillis) {
if (reportEpochs) {
double batchesPerSec = epochBatchCount / (epochTimeMillis / 1000.0);
double examplesPerSec = epochExampleCount / (epochTimeMillis / 1000.0);
double pcEtl = 100.0 * etlTotalTimeEpoch / (double) epochTimeMillis;
String etl = formatDurationMs(etlTotalTimeEpoch) + " ETL time" + (etlTotalTimeEpoch > 0 ? "(" + format2dp(pcEtl) + " %)" : "");
log.info("Epoch {} complete on iteration {} - {} batches ({} examples) in {} - {} batches/sec, {} examples/sec, {}",
at.epoch(), at.iteration(), epochBatchCount, epochExampleCount, formatDurationMs(epochTimeMillis),
format2dp(batchesPerSec), format2dp(examplesPerSec), etl);
}
return ListenerResponse.CONTINUE;
}
@Override
public void iterationStart(SameDiff sd, At at, MultiDataSet data, long etlMs) {
lastIterTime = System.currentTimeMillis();
etlTimeSumSinceLastReport += etlMs;
etlTotalTimeEpoch += etlMs;
}
@Override
public void iterationDone(SameDiff sd, At at, MultiDataSet dataSet, Loss loss) {
iterTimeSumSinceLastReport += System.currentTimeMillis() - lastIterTime;
epochBatchCount++;
if (dataSet.numFeatureArrays() > 0 && dataSet.getFeatures(0) != null) {
int n = (int) dataSet.getFeatures(0).size(0);
examplesSinceLastReportIter += n;
epochExampleCount += n;
}
if (at.iteration() > 0 && at.iteration() % frequency == 0) {
double l = loss.totalLoss();
String etl = "";
if (etlTimeSumSinceLastReport > 0) {
etl = "(" + formatDurationMs(etlTimeSumSinceLastReport) + " ETL";
if (frequency == 1) {
etl += ")";
} else {
etl += " in " + frequency + " iter)";
}
}
if(!reportIterPerformance) {
log.info("Loss at epoch {}, iteration {}: {}{}", at.epoch(), at.iteration(), format5dp(l), etl);
} else {
long time = System.currentTimeMillis();
if(lastReportTime > 0){
double batchPerSec = 1000 * frequency / (double)(time - lastReportTime);
double exPerSec = 1000 * examplesSinceLastReportIter / (double)(time - lastReportTime);
log.info("Loss at epoch {}, iteration {}: {}{}, batches/sec: {}, examples/sec: {}", at.epoch(), at.iteration(), format5dp(l),
etl, format5dp(batchPerSec), format5dp(exPerSec));
} else {
log.info("Loss at epoch {}, iteration {}: {}{}", at.epoch(), at.iteration(), format5dp(l), etl);
}
lastReportTime = time;
}
iterTimeSumSinceLastReport = 0;
etlTimeSumSinceLastReport = 0;
examplesSinceLastReportIter = 0;
}
}
protected String formatDurationMs(long ms) {
if (ms <= 100) {
return ms + " ms";
} else if (ms <= 60000L) {
double sec = ms / 1000.0;
return format2dp(sec) + " sec";
} else if (ms <= 60 * 60000L) {
double min = ms / 60_000.0;
return format2dp(min) + " min";
} else {
double hr = ms / 360_000.0;
return format2dp(hr) + " hr";
}
}
protected static final ThreadLocal<DecimalFormat> DF_2DP = new ThreadLocal<>();
protected static final ThreadLocal<DecimalFormat> DF_2DP_SCI = new ThreadLocal<>();
protected String format2dp(double d) {
if (d < 0.01) {
DecimalFormat f = DF_2DP_SCI.get();
if (f == null) {
f = new DecimalFormat("0.00E0");
DF_2DP.set(f);
}
return f.format(d);
} else {
DecimalFormat f = DF_2DP.get();
if (f == null) {
f = new DecimalFormat("#.00");
DF_2DP.set(f);
}
return f.format(d);
}
}
protected static final ThreadLocal<DecimalFormat> DF_5DP = new ThreadLocal<>();
protected static final ThreadLocal<DecimalFormat> DF_5DP_SCI = new ThreadLocal<>();
protected String format5dp(double d) {
if (d < 1e-4 || d > 1e4) {
//Use scientific
DecimalFormat f = DF_5DP_SCI.get();
if (f == null) {
f = new DecimalFormat("0.00000E0");
DF_5DP_SCI.set(f);
}
return f.format(d);
} else {
DecimalFormat f = DF_5DP.get();
if (f == null) {
f = new DecimalFormat("0.00000");
DF_5DP.set(f);
}
return f.format(d);
}
}
}
@@ -0,0 +1,331 @@
/*
* ******************************************************************************
* *
* *
* * This program and the accompanying materials are made available under the
* * terms of the Apache License, Version 2.0 which is available at
* * https://www.apache.org/licenses/LICENSE-2.0.
* *
* * See the NOTICE file distributed with this work for additional
* * information regarding copyright ownership.
* * Unless required by applicable law or agreed to in writing, software
* * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* * License for the specific language governing permissions and limitations
* * under the License.
* *
* * SPDX-License-Identifier: Apache-2.0
* *****************************************************************************
*/
package org.nd4j.autodiff.listeners.profiler;
import lombok.Getter;
import lombok.NonNull;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ArrayUtils;
import org.nd4j.autodiff.listeners.At;
import org.nd4j.autodiff.listeners.BaseListener;
import org.nd4j.autodiff.listeners.Loss;
import org.nd4j.autodiff.listeners.Operation;
import org.nd4j.autodiff.listeners.profiler.data.Phase;
import org.nd4j.autodiff.listeners.profiler.data.TraceEvent;
import org.nd4j.autodiff.samediff.SameDiff;
import org.nd4j.autodiff.samediff.internal.SameDiffOp;
import org.nd4j.common.base.Preconditions;
import org.nd4j.linalg.api.ndarray.INDArray;
import org.nd4j.linalg.api.ops.OpContext;
import org.nd4j.linalg.dataset.api.MultiDataSet;
import org.nd4j.common.primitives.AtomicBoolean;
import org.nd4j.shade.jackson.databind.DeserializationFeature;
import org.nd4j.shade.jackson.databind.MapperFeature;
import org.nd4j.shade.jackson.databind.ObjectMapper;
import org.nd4j.shade.jackson.databind.SerializationFeature;
import java.io.*;
import java.lang.management.ManagementFactory;
import java.util.*;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingDeque;
@Getter
@Slf4j
public class ProfilingListener extends BaseListener {
private final File outputFile;
private final boolean all;
private final int warmup;
private final int nIter;
private final long nMs;
private final Operation[] operations;
private final long pid;
private final long tid;
private Long firstOpStart = null; //Used for time termination
private int countTotalIter = 0;
private boolean logActive = false;
private long opStartNano;
private Writer writer;
private ObjectMapper json;
private final Thread fileWritingThread;
private final BlockingQueue<TraceEvent> writeQueue;
private final AtomicBoolean writing = new AtomicBoolean(false);
protected ProfilingListener(@NonNull File outputFile, boolean all, int warmup, int nIter, long nMs, Operation[] operations) {
Preconditions.checkArgument(!outputFile.exists(), "Output file already exists: %s", outputFile);
this.outputFile = outputFile;
this.all = all;
this.warmup = warmup;
this.nIter = nIter;
this.nMs = nMs;
this.operations = operations;
this.pid = getProcessId();
this.tid = Thread.currentThread().getId();
try {
this.writer = new BufferedWriter(new FileWriter(outputFile, false));
this.writer.write("["); //JSON array open (array close is optional for Chrome profiler format)
} catch (IOException e) {
throw new RuntimeException(e);
}
this.json = jsonMapper();
//Set up a queue so file access doesn't add latency to the execution thread
writeQueue = new LinkedBlockingDeque<>();
fileWritingThread = new Thread(new Runnable() {
@Override
public void run() {
try {
runHelper();
} catch (Throwable t) {
log.error("Error when attempting to write results to file", t);
}
}
public void runHelper() throws Exception {
while (true) {
TraceEvent te = writeQueue.take(); //Blocking
writing.set(true);
try {
String j = json.writeValueAsString(te);
writer.append(j);
writer.append(",\n");
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
writing.set(false);
}
}
}
});
fileWritingThread.setDaemon(true);
fileWritingThread.start();
}
@Override
public boolean isActive(Operation operation) {
return operations == null || ArrayUtils.contains(operations, operation);
}
@Override
public void operationStart(SameDiff sd, Operation op) {
this.logActive = operations == null || ArrayUtils.contains(operations, op);
}
@Override
public void operationEnd(SameDiff sd, Operation op) {
if (this.logActive) {
while ((!writeQueue.isEmpty() || writing.get()) && fileWritingThread.isAlive()) {
//Wait for file writing thread to catch up
try {
Thread.sleep(100);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
try {
writer.flush();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
this.logActive = false;
if (op == Operation.INFERENCE) {
//Increment for inference; iteration done is called only for TRAINING
countTotalIter++;
}
}
@Override
public void iterationDone(SameDiff sd, At at, MultiDataSet dataSet, Loss loss) {
//Increment for training
if (logActive) {
countTotalIter++;
}
}
@Override
public void preOpExecution(SameDiff sd, At at, SameDiffOp op, OpContext opContext) {
if (logActive) {
opStartNano = System.nanoTime();
if(!all && nMs > 0 && firstOpStart == null)
firstOpStart = opStartNano;
}
}
@Override
public void opExecution(SameDiff sd, At at, MultiDataSet batch, SameDiffOp op, OpContext opContext, INDArray[] outputs) {
if (logActive) {
long now = System.nanoTime();
if (warmup > 0 && countTotalIter < warmup) {
return; //Skip due to warmup phase
}
//Iteration termination
int terminationPt = this.nIter > 0 ? this.nIter : Integer.MAX_VALUE;
if (warmup > 0 && this.nIter > 0)
terminationPt += this.warmup;
if (countTotalIter > terminationPt) {
logActive = false;
return; //Skip due to max number of itertions
}
//Time termination
if(!all && nMs > 0 && (now - firstOpStart)/1000 > nMs) {
logActive = false;
return;
}
TraceEvent event = TraceEvent.builder()
.name(op.getOp().opName())
.categories(Collections.singletonList("Op"))
.ts(opStartNano / 1000)
.dur((now - opStartNano) / 1000)
.pid((int)pid)
.tid(tid)
.ph(Phase.X)
.args(Collections.<String, Object>singletonMap("name", op.getName()))
.build();
writeQueue.add(event);
}
}
private long getProcessId() {
// Note: may fail in some JVM implementations
// therefore fallback has to be provided
// something like '<pid>@<hostname>', at least in SUN / Oracle JVMs
final String jvmName = ManagementFactory.getRuntimeMXBean().getName();
final int index = jvmName.indexOf('@');
if (index < 1) {
// part before '@' empty (index = 0) / '@' not found (index = -1)
return 0;
}
try {
return Long.parseLong(jvmName.substring(0, index));
} catch (NumberFormatException e) {
// ignore
}
return 0;
}
/**
* Get a new JSON mapper for use in serializing/deserializing JSON format
*/
public static ObjectMapper jsonMapper() {
ObjectMapper json = new ObjectMapper();
json.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
json.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
json.configure(MapperFeature.SORT_PROPERTIES_ALPHABETICALLY, false);
json.disable(SerializationFeature.INDENT_OUTPUT); //One line
return json;
}
/**
* Create a new builder
* @param outputFile Output file. Will be overwritten if file already exists
*/
public static Builder builder(File outputFile) {
return new Builder(outputFile);
}
public static class Builder {
private final File outputFile;
private boolean all = true;
private int warmup = 0;
private int nIter = -1;
private long nMs = -1;
private Operation[] operations;
public Builder(@NonNull File outputFile) {
this.outputFile = outputFile;
}
/**
* If called, all data will be profiled with no limits (other than a warmup, if set)
*/
public Builder recordAll() {
this.all = true;
this.nIter = -1;
this.nMs = -1;
return this;
}
/**
* Specify the number of warmup iterations - i.e., these will be excluded from profiling results
*/
public Builder warmup(int iterations) {
this.warmup = iterations;
return this;
}
/**
* Set a limit on the maximum number of iterations to profile (after warmup, if any).
* Any ops executed after the specified number of iterations will not be profiled/recorded
*/
public Builder maxProfileIterations(int iterations) {
this.nIter = iterations;
this.all = false;
return this;
}
/**
* Set a limit on the maximum duration for profiling, in milliseconds.
* Any ops executed after the specified amount of time since the first (non-warmup) operation start will not be
* profiled/recorded
*/
public Builder maxProfilerMilliseconds(long ms) {
this.nMs = ms;
this.all = false;
return this;
}
/**
* Specify the operations (training, inference, etc) to profile.
* If not set, all operations are profiled
*/
public Builder operations(Operation... operations) {
this.operations = operations;
return this;
}
/**
* Create the profiling listener
*/
public ProfilingListener build() {
return new ProfilingListener(outputFile, all, warmup, nIter, nMs, operations);
}
}
}
@@ -0,0 +1,36 @@
/*
* ******************************************************************************
* *
* *
* * This program and the accompanying materials are made available under the
* * terms of the Apache License, Version 2.0 which is available at
* * https://www.apache.org/licenses/LICENSE-2.0.
* *
* * See the NOTICE file distributed with this work for additional
* * information regarding copyright ownership.
* * Unless required by applicable law or agreed to in writing, software
* * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* * License for the specific language governing permissions and limitations
* * under the License.
* *
* * SPDX-License-Identifier: Apache-2.0
* *****************************************************************************
*/
package org.nd4j.autodiff.listeners.profiler.comparison;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.nd4j.list.NDArrayList;
@AllArgsConstructor
@NoArgsConstructor
@Data
public class OpStats {
private String opInstanceName;
private String opName;
private int count;
private NDArrayList timesUs;
private Long sumUs;
}
@@ -0,0 +1,23 @@
/*
* ******************************************************************************
* *
* *
* * This program and the accompanying materials are made available under the
* * terms of the Apache License, Version 2.0 which is available at
* * https://www.apache.org/licenses/LICENSE-2.0.
* *
* * See the NOTICE file distributed with this work for additional
* * information regarding copyright ownership.
* * Unless required by applicable law or agreed to in writing, software
* * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* * License for the specific language governing permissions and limitations
* * under the License.
* *
* * SPDX-License-Identifier: Apache-2.0
* *****************************************************************************
*/
package org.nd4j.autodiff.listeners.profiler.data;
public enum ColorName {
}
@@ -0,0 +1,43 @@
/*
* ******************************************************************************
* *
* *
* * This program and the accompanying materials are made available under the
* * terms of the Apache License, Version 2.0 which is available at
* * https://www.apache.org/licenses/LICENSE-2.0.
* *
* * See the NOTICE file distributed with this work for additional
* * information regarding copyright ownership.
* * Unless required by applicable law or agreed to in writing, software
* * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* * License for the specific language governing permissions and limitations
* * under the License.
* *
* * SPDX-License-Identifier: Apache-2.0
* *****************************************************************************
*/
package org.nd4j.autodiff.listeners.profiler.data;
public enum Phase {
B,
E,
X,
I,
C,
b,
n,
e,
s,
t,
f,
P,
N,
O,
D,
M,
V,
v,
R,
c
}
@@ -0,0 +1,47 @@
/*
* ******************************************************************************
* *
* *
* * This program and the accompanying materials are made available under the
* * terms of the Apache License, Version 2.0 which is available at
* * https://www.apache.org/licenses/LICENSE-2.0.
* *
* * See the NOTICE file distributed with this work for additional
* * information regarding copyright ownership.
* * Unless required by applicable law or agreed to in writing, software
* * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* * License for the specific language governing permissions and limitations
* * under the License.
* *
* * SPDX-License-Identifier: Apache-2.0
* *****************************************************************************
*/
package org.nd4j.autodiff.listeners.profiler.data;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
import java.util.Map;
@Builder
@Data
@AllArgsConstructor
@NoArgsConstructor
public class TraceEvent {
private String name; //Name of event (usually op name)
private List<String> categories; //Comma separated list of categories
private Phase ph; //Event type - phase (see table for options)
private long ts; //Timestamp, in microseconds (us)
private Long dur; //Duration, optional
private Long tts; //Optional, thlread timestamp, in microseconds
private long pid; //Process ID
private long tid; //Thread ID
private Map<String, Object> args; //Args
private ColorName cname; //Optional, color name (must be one of reserved color names: https://github.com/catapult-project/catapult/blob/master/tracing/tracing/base/color_scheme.html )
}
@@ -0,0 +1,33 @@
/*
* ******************************************************************************
* *
* *
* * This program and the accompanying materials are made available under the
* * terms of the Apache License, Version 2.0 which is available at
* * https://www.apache.org/licenses/LICENSE-2.0.
* *
* * See the NOTICE file distributed with this work for additional
* * information regarding copyright ownership.
* * Unless required by applicable law or agreed to in writing, software
* * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* * License for the specific language governing permissions and limitations
* * under the License.
* *
* * SPDX-License-Identifier: Apache-2.0
* *****************************************************************************
*/
package org.nd4j.autodiff.listeners.profiler.data;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
@AllArgsConstructor
@NoArgsConstructor
@Data
public class TraceEvents {
private List<TraceEvent> traceEvents;
}
@@ -0,0 +1,241 @@
/*
* ******************************************************************************
* *
* *
* * This program and the accompanying materials are made available under the
* * terms of the Apache License, Version 2.0 which is available at
* * https://www.apache.org/licenses/LICENSE-2.0.
* *
* * See the NOTICE file distributed with this work for additional
* * information regarding copyright ownership.
* * Unless required by applicable law or agreed to in writing, software
* * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* * License for the specific language governing permissions and limitations
* * under the License.
* *
* * SPDX-License-Identifier: Apache-2.0
* *****************************************************************************
*/
package org.nd4j.autodiff.listeners.records;
import org.nd4j.shade.guava.base.Predicates;
import org.nd4j.shade.guava.collect.Collections2;
import org.nd4j.shade.guava.collect.ImmutableMap;
import org.nd4j.shade.guava.collect.Lists;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import lombok.Getter;
import org.nd4j.autodiff.samediff.SDVariable;
import org.nd4j.common.base.Preconditions;
import org.nd4j.evaluation.IEvaluation;
import org.nd4j.evaluation.IMetric;
@Getter
public class EvaluationRecord {
private Map<String, List<IEvaluation>> evaluations;
private Map<Class<? extends IEvaluation>, IEvaluation> classEvaluations = new HashMap<>();
private boolean isEmpty = true;
public EvaluationRecord(Map<String, List<IEvaluation>> evaluations) {
this.evaluations = Collections.unmodifiableMap(evaluations);
for (List<IEvaluation> le : evaluations.values()) {
for (IEvaluation e : le) {
isEmpty = false;
if (classEvaluations.containsKey(e.getClass()))
classEvaluations.remove(e.getClass());
else
classEvaluations.put(e.getClass(), e);
}
}
}
private EvaluationRecord() {
}
public boolean isEmpty() {
return isEmpty;
}
/**
* Get all evaluations
*/
public Map<String, List<IEvaluation>> evaluations() {
return evaluations;
}
/**
* Get evaluations for a given param/variable
*
* @param param The target param/variable
*/
public List<IEvaluation> evaluations(String param) {
Preconditions.checkArgument(evaluations.containsKey(param),
"No evaluations for %s.", param);
return evaluations.get(param);
}
/**
* Get evaluations for a given param/variable
*
* @param param The target param/variable
*/
public List<IEvaluation> evaluations(SDVariable param) {
return evaluations(param.name());
}
/**
* Get the evaluation for param at the specified index
*/
public IEvaluation evaluation(String param, int index) {
return evaluations(param).get(index);
}
/**
* Get the evaluation for param at the specified index
*/
public IEvaluation evaluation(SDVariable param, int index) {
return evaluation(param.name(), index);
}
/**
* Get the evaluation for a given param/variable
* <p>
* Will throw an exception if there are more than one or no evaluations for the param
*
* @param param The target param/variable
*/
public <T extends IEvaluation> T evaluation(String param) {
Preconditions.checkArgument(evaluations.containsKey(param),
"No evaluations for %s.", param);
Preconditions.checkArgument(evaluations.get(param).size() == 1,
"Multiple evaluations for %s. Use evaluations().", param);
return (T) evaluations.get(param).get(0);
}
/**
* Get the evaluation for a given param/variable
* <p>
* Will throw an exception if there are more than one or no evaluations for the param
*
* @param param The target param/variable
*/
public <T extends IEvaluation> T evaluation(SDVariable param) {
return evaluation(param.name());
}
/**
* Get the evaluation of a given type
* <p>
* Will throw an exception if there are more than one or no evaluations of that type
*
* @param evalClass The type of evaluation to look for
*/
public <T extends IEvaluation<T>> T evaluation(Class<T> evalClass) {
Preconditions.checkArgument(classEvaluations.containsKey(evalClass),
"Can't get evaluation for %s. Either no evaluations with that class are present, or more than one are.", evalClass);
return (T) classEvaluations.get(evalClass);
}
/**
* Get the evaluation of a given type, for a given param/variable
* <p>
* Will throw an exception if there are more than one or no evaluations of that type for the given param
*
* @param param The target param/variable
* @param evalClass The type of evaluation to look for
*/
public <T extends IEvaluation<T>> T evaluation(String param, Class<T> evalClass) {
Collection<IEvaluation> evals = Collections2.filter(evaluations(param), Predicates.instanceOf(evalClass));
Preconditions.checkArgument(evals.size() == 1, "Multiple or no evaluations of type %s for param %s.", evalClass, param);
return (T) evals.iterator().next();
}
/**
* Get the evaluation of a given type, for a given param/variable
* <p>
* Will throw an exception if there are more than one or no evaluations of that type for the given param
*
* @param param The target param/variable
* @param evalClass The type of evaluation to look for
*/
public <T extends IEvaluation<T>> T evaluation(SDVariable param, Class<T> evalClass) {
return evaluation(param.name(), evalClass);
}
/**
* Get the metric's value for the evaluation of the metric's type
* <p>
* Will throw an exception if there are more than one or no evaluations of that type
*
* @param metric The metric to calculate
*/
public double getValue(IMetric metric) {
return evaluation(metric.getEvaluationClass()).getValue(metric);
}
/**
* Get the metric's value for the evaluation of the metric's type, for a given param/variable
* <p>
* Will throw an exception if there are more than one or no evaluations of that type for the given param
*
* @param param The target param/variable
* @param metric The metric to calculate
*/
public double getValue(String param, IMetric metric) {
return evaluation(param, metric.getEvaluationClass()).getValue(metric);
}
/**
* Get the metric's value for the evaluation of the metric's type, for a given param/variable
* <p>
* Will throw an exception if there are more than one or no evaluations of that type for the given param
*
* @param param The target param/variable
* @param metric The metric to calculate
*/
public double getValue(SDVariable param, IMetric metric) {
return getValue(param.name(), metric);
}
/**
* Get the metric's value for the evaluation for a given param/variable at the given index
* <p>
* Will throw an exception if the target evaluation doesn't support the given metric
*
* @param param The target param/variable
* @param index The index of the target evaluation on the param
* @param metric The metric to calculate
*/
public double getValue(String param, int index, IMetric metric) {
return evaluation(param, index).getValue(metric);
}
/**
* Get the metric's value for the evaluation for a given param/variable at the given index
* <p>
* Will throw an exception if the target evaluation doesn't support the given metric
*
* @param param The target param/variable
* @param index The index of the target evaluation on the param
* @param metric The metric to calculate
*/
public double getValue(SDVariable param, int index, IMetric metric) {
return getValue(param.name(), index, metric);
}
}
@@ -0,0 +1,355 @@
/*
* ******************************************************************************
* *
* *
* * This program and the accompanying materials are made available under the
* * terms of the Apache License, Version 2.0 which is available at
* * https://www.apache.org/licenses/LICENSE-2.0.
* *
* * See the NOTICE file distributed with this work for additional
* * information regarding copyright ownership.
* * Unless required by applicable law or agreed to in writing, software
* * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* * License for the specific language governing permissions and limitations
* * under the License.
* *
* * SPDX-License-Identifier: Apache-2.0
* *****************************************************************************
*/
package org.nd4j.autodiff.listeners.records;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import lombok.Getter;
import org.nd4j.autodiff.listeners.Listener;
import org.nd4j.autodiff.samediff.SDVariable;
import org.nd4j.autodiff.samediff.SameDiff;
import org.nd4j.common.base.Preconditions;
import org.nd4j.evaluation.IEvaluation;
import org.nd4j.evaluation.IMetric;
import org.nd4j.linalg.dataset.api.iterator.DataSetIterator;
@Getter
public class History {
private List<EvaluationRecord> trainingHistory;
private List<EvaluationRecord> validationHistory;
private LossCurve lossCurve;
private long trainingTimeMillis;
private List<Long> validationTimesMillis;
public History(List<EvaluationRecord> training, List<EvaluationRecord> validation, LossCurve loss,
long trainingTimeMillis, List<Long> validationTimesMillis){
trainingHistory = Collections.unmodifiableList(training);
validationHistory = Collections.unmodifiableList(validation);
this.lossCurve = loss;
this.trainingTimeMillis = trainingTimeMillis;
this.validationTimesMillis = Collections.unmodifiableList(validationTimesMillis);
}
/**
* Get the training evaluations
*/
public List<EvaluationRecord> trainingEval(){
return trainingHistory;
}
/**
* Get the validation evaluations
*/
public List<EvaluationRecord> validationEval(){
return validationHistory;
}
/**
* Get the loss curve
*/
public LossCurve lossCurve(){
return lossCurve;
}
/**
* Get the total training time, in milliseconds
*/
public long trainingTimeMillis(){
return trainingTimeMillis;
}
/**
* Get the total validation time, in milliseconds
*/
public List<Long> validationTimesMillis(){
return validationTimesMillis;
}
/**
* Get the number of epochs trained for
*/
public int trainingEpochs(){
return trainingHistory.size();
}
/**
* Get the number of epochs validation was ran on
*/
public int validationEpochs(){
return validationHistory.size();
}
/**
* Get the results of a training evaluation on a given parameter for a given metric
*
* Only works if there is only one evaluation with the given metric for param
*/
public List<Double> trainingEval(String param, IMetric metric) {
List<Double> data = new ArrayList<>();
for(EvaluationRecord er : trainingHistory)
data.add(er.getValue(param, metric));
return data;
}
/**
* Get the results of a training evaluation on a given parameter for a given metric
*
* Only works if there is only one evaluation with the given metric for param
*/
public List<Double> trainingEval(SDVariable param, IMetric metric){
return trainingEval(param.name(), metric);
}
/**
* Get the results of a training evaluation on a given parameter at a given index, for a given metric
*
* Note that it returns all recorded evaluations.
* Index determines the evaluation used not the epoch's results to return.
*/
public List<Double> trainingEval(String param, int index, IMetric metric) {
List<Double> data = new ArrayList<>();
for(EvaluationRecord er : trainingHistory)
data.add(er.getValue(param, index, metric));
return data;
}
/**
* Get the results of a training evaluation on a given parameter at a given index, for a given metric
*
* Note that it returns all recorded evaluations.
* Index determines the evaluation used not the epoch's results to return.
*/
public List<Double> trainingEval(SDVariable param, int index, IMetric metric) {
return trainingEval(param.name(), index, metric);
}
/**
* Get the results of a training evaluation for a given metric
*
* Only works if there is only one evaluation with the given metric
*/
public List<Double> trainingEval(IMetric metric) {
List<Double> data = new ArrayList<>();
for(EvaluationRecord er : trainingHistory)
data.add(er.getValue(metric));
return data;
}
/**
* Get the results of a training evaluation on a given parameter
*
* Only works if there is only one evaluation for param.
*/
public List<IEvaluation> trainingEval(String param) {
List<IEvaluation> data = new ArrayList<>();
for(EvaluationRecord er : trainingHistory)
data.add(er.evaluation(param));
return data;
}
/**
* Get the results of a training evaluation on a given parameter
*
* Only works if there is only one evaluation for param.
*/
public List<IEvaluation> trainingEval(SDVariable param){
return trainingEval(param.name());
}
/**
* Get the results of a training evaluation on a given parameter at a given index
*
* Note that it returns all recorded evaluations.
* Index determines the evaluation used not the epoch's results to return.
*/
public List<IEvaluation> trainingEval(String param, int index) {
List<IEvaluation> data = new ArrayList<>();
for(EvaluationRecord er : trainingHistory)
data.add(er.evaluation(param, index));
return data;
}
/**
* Get the results of a training evaluation on a given parameter at a given index
*
* Note that it returns all recorded evaluations.
* Index determines the evaluation used not the epoch's results to return.
*/
public List<IEvaluation> trainingEval(SDVariable param, int index){
return trainingEval(param.name(), index);
}
/**
* Get the results of a validation evaluation on a given parameter for a given metric
*
* Only works if there is only one evaluation with the given metric for param
*/
public List<Double> validationEval(String param, IMetric metric) {
List<Double> data = new ArrayList<>();
for(EvaluationRecord er : validationHistory)
data.add(er.getValue(param, metric));
return data;
}
/**
* Get the results of a validation evaluation on a given parameter for a given metric
*
* Only works if there is only one evaluation with the given metric for param
*/
public List<Double> validationEval(SDVariable param, IMetric metric) {
return validationEval(param.name(), metric);
}
/**
* Get the results of a validation evaluation on a given parameter at a given index, for a given metric
*
* Note that it returns all recorded evaluations.
* Index determines the evaluation used not the epoch's results to return.
*/
public List<Double> validationEval(String param, int index, IMetric metric) {
List<Double> data = new ArrayList<>();
for(EvaluationRecord er : validationHistory)
data.add(er.getValue(param, index, metric));
return data;
}
/**
* Get the results of a validation evaluation on a given parameter at a given index, for a given metric
*
* Note that it returns all recorded evaluations.
* Index determines the evaluation used not the epoch's results to return.
*/
public List<Double> validationEval(SDVariable param, int index, IMetric metric) {
return validationEval(param.name(), index, metric);
}
/**
* Get the results of a validation evaluation for a given metric
*
* Only works if there is only one evaluation with the given metric
*/
public List<Double> validationEval(IMetric metric) {
List<Double> data = new ArrayList<>();
for(EvaluationRecord er : validationHistory)
data.add(er.getValue(metric));
return data;
}
/**
* Get the results of a validation evaluation on a given parameter
*
* Only works if there is only one evaluation for param.
*/
public List<IEvaluation> validationEval(String param) {
List<IEvaluation> data = new ArrayList<>();
for(EvaluationRecord er : validationHistory)
data.add(er.evaluation(param));
return data;
}
/**
* Get the results of a validation evaluation on a given parameter
*
* Only works if there is only one evaluation for param.
*/
public List<IEvaluation> validationEval(SDVariable param){
return validationEval(param.name());
}
/**
* Get the results of a validation evaluation on a given parameter at a given index
*
* Note that it returns all recorded evaluations.
* Index determines the evaluation used not the epoch's results to return.
*/
public List<IEvaluation> validationEval(String param, int index) {
List<IEvaluation> data = new ArrayList<>();
for(EvaluationRecord er : validationHistory)
data.add(er.evaluation(param, index));
return data;
}
/**
* Get the results of a validation evaluation on a given parameter at a given index
*
* Note that it returns all recorded evaluations.
* Index determines the evaluation used not the epoch's results to return.
*/
public List<IEvaluation> validationEval(SDVariable param, int index){
return validationEval(param.name(), index);
}
/**
* Gets the training evaluations ran during the last epoch
*/
public EvaluationRecord finalTrainingEvaluations() {
Preconditions.checkState(!trainingHistory.isEmpty(), "Cannot get final training evaluation - history is empty");
return trainingHistory.get(trainingHistory.size() - 1);
}
/**
* Gets the validation evaluations ran during the last epoch
*/
public EvaluationRecord finalValidationEvaluations() {
Preconditions.checkState(!validationHistory.isEmpty(), "Cannot get final validation evaluation - history is empty");
return validationHistory.get(validationHistory.size() - 1);
}
/**
* Gets the evaluation record for a given epoch.
* @param epoch The epoch to get results for. If negative, returns results for the epoch that many epochs from the end.
*/
public EvaluationRecord trainingEvaluations(int epoch) {
if(epoch >= 0){
return trainingHistory.get(epoch);
} else {
return trainingHistory.get(trainingHistory.size() - epoch);
}
}
/**
* Gets the evaluation record for a given epoch.
* @param epoch The epoch to get results for. If negative, returns results for the epoch that many epochs from the end.
*/
public EvaluationRecord validationEvaluations(int epoch) {
if(epoch >= 0){
return trainingHistory.get(epoch);
} else {
return validationHistory.get(validationHistory.size() - epoch);
}
}
}
@@ -0,0 +1,215 @@
/*
* ******************************************************************************
* *
* *
* * This program and the accompanying materials are made available under the
* * terms of the Apache License, Version 2.0 which is available at
* * https://www.apache.org/licenses/LICENSE-2.0.
* *
* * See the NOTICE file distributed with this work for additional
* * information regarding copyright ownership.
* * Unless required by applicable law or agreed to in writing, software
* * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* * License for the specific language governing permissions and limitations
* * under the License.
* *
* * SPDX-License-Identifier: Apache-2.0
* *****************************************************************************
*/
package org.nd4j.autodiff.listeners.records;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import lombok.Getter;
import lombok.NonNull;
import org.nd4j.autodiff.listeners.Loss;
import org.nd4j.autodiff.samediff.SDVariable;
import org.nd4j.common.base.Preconditions;
import org.nd4j.linalg.api.buffer.DataType;
import org.nd4j.linalg.api.ndarray.INDArray;
import org.nd4j.linalg.factory.Nd4j;
public class LossCurve {
@Getter
private List<String> lossNames;
@Getter
private INDArray lossValues;
public LossCurve(List<Loss> losses) {
lossNames = Collections.unmodifiableList(losses.get(0).getLossNames());
int numLossValues = losses.get(0).lossValues().length;
lossValues = Nd4j.create(DataType.FLOAT, losses.size(), losses.get(0).lossValues().length);
for(int i = 0 ; i < losses.size() ; i++) {
Loss l = losses.get(i);
Preconditions.checkArgument(l.getLossNames().equals(lossNames),
"Loss names for loss %s differ from others. Expected %s, got %s",
i, lossNames, l.getLossNames());
Preconditions.checkArgument(l.getLosses().length == numLossValues,
"Number of loss values for loss %s differ from others. Expected %s, got %s",
i, numLossValues, l.getLosses().length);
lossValues = lossValues.putRow(i, Nd4j.createFromArray(l.getLosses()).castTo(DataType.FLOAT));
}
}
public LossCurve(double[] lossValues, List<String> lossNames) {
this.lossValues = Nd4j.createFromArray(new double[][]{ lossValues}).castTo(DataType.FLOAT);
this.lossNames = lossNames;
}
protected LossCurve(INDArray lossValues, List<String> lossNames) {
Preconditions.checkArgument(lossValues.rank() == 2, "lossValues must have a rank of 2, got %s", lossValues.rank());
Preconditions.checkArgument(lossValues.dataType() == DataType.FLOAT, "lossValues must be type FLOAT, got %s", lossValues.dataType());
this.lossValues = lossValues;
this.lossNames = lossNames;
}
public List<Loss> losses(){
List<Loss> losses = new ArrayList<>();
for(int i = 0 ; i < lossValues.size(0) ; i++){
losses.add(new Loss(lossNames, lossValues.getRow(i).toDoubleVector()));
}
return losses;
}
/**
* Get the mean loss for a given epoch
*
* If epoch is negative, counts backwards from the end.
* E.g. losses(-1) gets the last epoch.
*
* @param epoch The epoch to get. If negative, returns results for the epoch that many epochs from the end
*/
public Loss meanLoss(int epoch){
if(epoch >= 0){
return new Loss(lossNames, lossValues.getRow(epoch).toDoubleVector());
} else {
return new Loss(lossNames, lossValues.getRow(lossValues.rows() + epoch).toDoubleVector());
}
}
/**
* Get the mean loss for the last epoch.
*/
public Loss lastMeanLoss(){
return meanLoss(-1);
}
/**
* Return all mean loss values for a given variable
*/
public float[] meanLoss(@NonNull String lossName){
int idx = lossNames.indexOf(lossName);
Preconditions.checkArgument(idx >= 0, "No loss value for %s. Existing losses: %s", lossName, lossNames);
float[] loss = new float[(int) lossValues.size(0)];
for(int i = 0 ; i < lossValues.size(0) ; i++){
loss[i] = lossValues.getFloat(i, idx);
}
return loss;
}
/**
* Return all mean loss values for a given variable
*/
public float[] meanLoss(@NonNull SDVariable loss){
return meanLoss(loss.name());
}
/**
* Return the mean loss value for a given variable on a given epoch.
*
* See {@link #meanLoss(int)}
*/
public float meanLoss(@NonNull String lossName, int epoch){
int idx = lossNames.indexOf(lossName);
Preconditions.checkArgument(idx >= 0, "No loss value for %s. Existing losses: %s", lossName, lossNames);
if(epoch >= 0) {
return lossValues.getFloat(epoch, idx);
} else {
return lossValues.getFloat(lossValues.rows() + epoch, idx);
}
}
/**
* Return the mean loss value for a given variable on a given epoch.
*
* See {@link #meanLoss(int)}
*/
public float meanLoss(@NonNull SDVariable loss, int epoch){
return meanLoss(loss.name(), epoch);
}
/**
* Return the mean loss value for a given variable on the last epoch.
*/
public float lastMeanLoss(@NonNull String lossName){
int idx = lossNames.indexOf(lossName);
Preconditions.checkArgument(idx >= 0, "No loss value for %s. Existing losses: %s", lossName, lossNames);
return lossValues.getFloat(lossValues.rows() - 1, idx);
}
/**
* Return the mean loss value for a given variable on the last epoch.
*/
public float lastMeanLoss(@NonNull SDVariable loss){
return lastMeanLoss(loss.name());
}
/**
* Return the loss delta between the last epoch and the one before it.
* Equivalent to meanLoss(-1) - meanLoss(-2).
* A positive delta means the loss is increasing, and a negative delta means it is decreasing.
*/
public Loss lastMeanDelta(){
return lastMeanLoss().sub(meanLoss(-2));
}
/**
* Return the loss delta between the last epoch and the one before it, for a given variable.
* Equivalent to meanLoss(-1) - meanLoss(-2).
* A positive delta means the loss is increasing, and a negative delta means it is decreasing.
*/
public double lastMeanDelta(String lossName){
return lastMeanDelta().getLoss(lossName);
}
/**
* Return the loss delta between the last epoch and the one before it, for a given variable.
* Equivalent to meanLoss(-1) - meanLoss(-2).
* A positive delta means the loss is increasing, and a negative delta means it is decreasing.
*/
public double lastMeanDelta(SDVariable loss){
return lastMeanDelta(loss.name());
}
/**
* Return a new LossCurve with the given losses added on as the most recent epoch
*/
public LossCurve addLossAndCopy(Loss loss){
return addLossAndCopy(loss.getLosses(), loss.lossNames());
}
/**
* Return a new LossCurve with the given losses added on as the most recent epoch
*/
public LossCurve addLossAndCopy(double[] values, List<String> lossNames){
return new LossCurve(
Nd4j.concat(0, lossValues,
Nd4j.createFromArray(new double[][]{values}).castTo(DataType.FLOAT)),
lossNames);
}
}
@@ -0,0 +1,58 @@
/*
* ******************************************************************************
* *
* *
* * This program and the accompanying materials are made available under the
* * terms of the Apache License, Version 2.0 which is available at
* * https://www.apache.org/licenses/LICENSE-2.0.
* *
* * See the NOTICE file distributed with this work for additional
* * information regarding copyright ownership.
* * Unless required by applicable law or agreed to in writing, software
* * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* * License for the specific language governing permissions and limitations
* * under the License.
* *
* * SPDX-License-Identifier: Apache-2.0
* *****************************************************************************
*/
package org.nd4j.autodiff.loss;
public enum LossReduce {
/**
* No reduction. In most cases, output is the same shape as the predictions/labels.<br>
* Weights (if any) are applied<br>
* Example Input: 2d input array with mean squared error loss.<br>
* Example Output: squaredDifference(predictions,labels), with same shape as input/labels<br>
*/
NONE,
/**
* Weigted sum across all loss values, returning a scalar.<br>
*/
SUM,
/**
* Weighted mean: sum(weights * perOutputLoss) / sum(weights) - gives a single scalar output<br>
* Example: 2d input, mean squared error<br>
* Output: squared_error_per_ex = weights * squaredDifference(predictions,labels)<br>
* output = sum(squared_error_per_ex) / sum(weights)<br>
* <br>
* NOTE: if weights array is not provided, then weights default to 1.0 for all entries - and hence
* MEAN_BY_WEIGHT is equivalent to MEAN_BY_NONZERO_WEIGHT_COUNT
*/
MEAN_BY_WEIGHT,
/**
* Weighted mean: sum(weights * perOutputLoss) / count(weights != 0)<br>
* Example: 2d input, mean squared error loss.<br>
* Output: squared_error_per_ex = weights * squaredDifference(predictions,labels)<br>
* output = sum(squared_error_per_ex) / count(weights != 0)<br>
*
* NOTE: if weights array is not provided, then weights default to scalar 1.0 and hence MEAN_BY_NONZERO_WEIGHT_COUNT
* is equivalent to MEAN_BY_WEIGHT
*/
MEAN_BY_NONZERO_WEIGHT_COUNT
}
@@ -0,0 +1,25 @@
/*
* ******************************************************************************
* *
* *
* * This program and the accompanying materials are made available under the
* * terms of the Apache License, Version 2.0 which is available at
* * https://www.apache.org/licenses/LICENSE-2.0.
* *
* * See the NOTICE file distributed with this work for additional
* * information regarding copyright ownership.
* * Unless required by applicable law or agreed to in writing, software
* * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* * License for the specific language governing permissions and limitations
* * under the License.
* *
* * SPDX-License-Identifier: Apache-2.0
* *****************************************************************************
*/
package org.nd4j.autodiff.samediff;
public interface ArgumentInterceptor {
SDVariable intercept(SDVariable argument);
}
@@ -0,0 +1,81 @@
/*
* ******************************************************************************
* *
* *
* * This program and the accompanying materials are made available under the
* * terms of the Apache License, Version 2.0 which is available at
* * https://www.apache.org/licenses/LICENSE-2.0.
* *
* * See the NOTICE file distributed with this work for additional
* * information regarding copyright ownership.
* * Unless required by applicable law or agreed to in writing, software
* * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* * License for the specific language governing permissions and limitations
* * under the License.
* *
* * SPDX-License-Identifier: Apache-2.0
* *****************************************************************************
*/
package org.nd4j.autodiff.samediff;
import org.nd4j.linalg.api.ndarray.INDArray;
import java.util.Collection;
public interface ArrayHolder {
/**
* @return True if an array by that name exists
*/
boolean hasArray(String name);
/**
* @param name Name of the array to get
* @return The array, or null if no array with that name exists
*/
INDArray getArray(String name);
/**
* Set the array for the specified name (new array, or replace if it already exists)
*
* @param name Name of the array
* @param array Array to set
*/
void setArray(String name, INDArray array);
/**
* Remove the array from the ArrayHolder, returning it (if it exists)
*
* @param name Name of the array to return
* @return The now-removed array
*/
INDArray removeArray(String name);
/**
* @return Number of arrays in the ArrayHolder
*/
int size();
/**
* Initialize from the specified array holder.
* This clears all internal arrays, and adds all arrays from the specified array holder
*
* @param arrayHolder Array holder to initialize this based on
*/
void initFrom(ArrayHolder arrayHolder);
/**
* @return Names of the arrays currently in the ArrayHolder
*/
Collection<String> arrayNames();
/**
* Rename the entry with the specified name
*
* @param from Original name
* @param to New name
*/
void rename(String from, String to);
}
@@ -0,0 +1,18 @@
package org.nd4j.autodiff.samediff;
import lombok.Data;
import java.util.HashMap;
import java.util.Map;
@Data
public class ConditionEvaluation {
private int iteration;
private String conditionOperation;
private Object conditionValue;
private String conditionSource;
private boolean terminationTriggered;
private Map<String, Object> inputValues = new HashMap<>();
private String evaluationContext;
private long timestamp;
}
@@ -0,0 +1,636 @@
/*
* ******************************************************************************
* *
* *
* * This program and the accompanying materials are made available under the
* * terms of the Apache License, Version 2.0 which is available at
* * https://www.apache.org/licenses/LICENSE-2.0.
* *
* * See the NOTICE file distributed with this work for additional
* * information regarding copyright ownership.
* * Unless required by applicable law or agreed to in writing, software
* * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* * License for the specific language governing permissions and limitations
* * under the License.
* *
* * SPDX-License-Identifier: Apache-2.0
* *****************************************************************************
*/
package org.nd4j.autodiff.samediff;
import lombok.Builder;
import lombok.Data;
import lombok.NonNull;
import org.nd4j.autodiff.samediff.internal.SameDiffOp;
import org.nd4j.common.base.Preconditions;
import org.nd4j.linalg.api.buffer.DataType;
import org.nd4j.linalg.api.ops.custom.Invoke;
import org.nd4j.linalg.api.ops.impl.controlflow.compat.Enter;
import org.nd4j.linalg.api.ops.impl.controlflow.compat.Exit;
import org.nd4j.linalg.api.ops.impl.controlflow.compat.Merge;
import org.nd4j.linalg.api.ops.impl.controlflow.compat.NextIteration;
import org.nd4j.linalg.factory.Nd4j;
import org.nd4j.shade.guava.collect.Sets;
import java.util.*;
import java.util.stream.Collectors;
/**
* Top level class for looping constructs in samediff.
* This includes the ability to create for and while loops as well as
* encapsulate the usage of invoke as a function body. This spec can be read here:
* https://github.com/onnx/onnx/blob/main/docs/Operators.md#Loop
*
* The core components of the looping function are as follows:
* 1. Loop variables:
* a. current iteration (gets updated during loop body) (defaults to 0)
* b. max number of iterations (defaults to {@link Long#MAX_VALUE}
* c. a current condition a user passes in and is updated during lambda invocation
* Any variables beyond the first 3 are extra variables by the user
*
*
*/
public class ControlFlow {
/**
* Initializes the loop variables with default parameters. The variables are as follows:
* current iteration
* max number of iterations
* extra condition to use
*
*
*
* The passed in variable names will be assumed to be names for each of these variables
* mentioned above respectively. Please ensure that these are the intended names
* of the variables.
* @param namesToUse the names of the variables to use. Must be length 2.
* @param loopBody the loop body to initialize
* @param maxIterations the max iterations to iterate over
*/
public static SDVariable[] initializeLoopBody(String[] namesToUse,SameDiff loopBody,int maxIterations) {
Preconditions.checkState( namesToUse != null && namesToUse.length == 2,"Number of input names must be 2.");
SDVariable[] ret = new SDVariable[] {
loopBody.constant(namesToUse[1], maxIterations),
loopBody.var(namesToUse[0], Nd4j.zeros(1)),
};
return ret;
}
/**
* Initializes the loop variables with default parameters. The variables are as follows:
* current iteration
* max number of iterations
* extra condition to use
*
* The passed in variable names will be assumed to be names for each of these variables
* mentioned above respectively. Please ensure that these are the intended names
* of the variables.
* @param namesToUse the names of the variables to use. Must be length 3.
* @param loopBody the loop body to initialize
* @param maxIterations the max iterations to iterate over
* @param extraCond the extra condition to use
*/
public static SDVariable[] initializeLoopBody(String[] namesToUse,SameDiff loopBody,int maxIterations,boolean extraCond) {
Preconditions.checkState( namesToUse != null && namesToUse.length == 3,"Number of input names must be 3.");
SDVariable[] ret = new SDVariable[] {
loopBody.var(namesToUse[0], Nd4j.zeros(1)),
loopBody.constant(namesToUse[1], maxIterations),
loopBody.constant(namesToUse[2], extraCond)
};
return ret;
}
/**
* Create the arguments used in {@link #condBody()}
* and {@link #loopWithConditions(String[], String, SameDiff, SameDiff, String, SDVariable[], String[], String[])}
* @param maxIterations the max number of iterations
* @param condIn the input conditions
* @param startIterations the start iterations
* @param extraArgs the extra arguments for the user
* @return the ordered arguments
*/
public static SDVariable[] args(SDVariable maxIterations,SDVariable condIn,SDVariable startIterations,SDVariable[] extraArgs) {
return LoopArgs.builder().extraArgs(extraArgs)
.condIn(condIn)
.maxIters(maxIterations)
.startIter(startIterations).build().toArgs();
}
/**
* Constructs a If statement using the tensorflow style control flow operations (Switch and Merge)
*
* If the result of cond is true, returns the result of trueBody, otherwise returns the result of falseBody
*
* Note that cond and body lambdas are only called once to construct the graph. The constructed graph is used to evaluate.
*
* See <a href="http://download.tensorflow.org/paper/white_paper_tf_control_flow_implementation_2017_11_1.pdf">Tensorflow Control Flow Implementation</a>
*
* @param outputName Name to give the output variable. If null, doesn't rename
* @param ifName The name of the if block. If null, uses "if"
* @param cond A lambda evaluating to the if condition
* @param trueBody A lambda to be executed if cond is true (the if block)
* @param falseBody A lambda to be executed if cond is false (the else block)
* @return The value of trueBody if cond is true, or falseBody if it isn't
*/
public static SDVariable ifCond(SameDiff sameDiff,String outputName, String ifName, @NonNull SameDiffNoArgSingleLambda cond,
@NonNull SameDiffNoArgSingleLambda trueBody, @NonNull SameDiffNoArgSingleLambda falseBody){
ifName = sameDiff.newBlockName(ifName == null ? "if" : ifName);
NameScope ifScope = sameDiff.withNameScope(ifName);
NameScope condScope = sameDiff.withNameScope("cond");
final SDVariable pred = cond.define(sameDiff);
condScope.close();
if (pred.dataType() != DataType.BOOL) {
//cleanup partially added block
for(SDVariable v : sameDiff.getVariablesInScope(ifScope))
sameDiff.getVariables().remove(v.name());
for(SameDiffOp op : sameDiff.getOpsInScope(ifScope)) {
for(String in : op.getInputsToOp()){
sameDiff.removeArgFromOp(in, op.getOp());
}
sameDiff.getOps().remove(op.getName());
}
throw new IllegalStateException("Can not use " + pred.name()
+ " as the condition of an If statement, the condition must be a boolean.");
}
final Map<String, SDVariable[]> switches = new HashMap<>();
final Set<String> declared = Sets.newHashSet(sameDiff.variableMap().keySet());
sameDiff.addArgumentInterceptor(argument -> {
if(argument == null)
return null;
// if its declared in the if, we don't care about it
if(declared == null || !declared.contains(argument.name()))
return argument;
// if we've already added a switch, move on
if(switches.containsKey(argument.name()))
return switches.get(argument.name())[1];
SDVariable[] s = sameDiff.switchOp(argument, pred);
switches.put(argument.name(), s);
return s[1];
});
NameScope trueScope = sameDiff.withNameScope("trueBody");
SDVariable trueOut = trueBody.define(sameDiff);
sameDiff.removeArgumentInterceptor();
if(declared.contains(trueOut.name())) {
SDVariable[] s = sameDiff.switchOp(trueOut, pred);
switches.put(trueOut.name(), s);
trueOut = s[1];
}
trueScope.close();
final Set<String> declared2 = Sets.newHashSet(sameDiff.variableMap().keySet());
sameDiff.addArgumentInterceptor(argument -> {
// if its declared in the if, we don't care about it
if(!declared2.contains(argument.name()))
return argument;
// if we've already added a switch, move on
if(switches.containsKey(argument.name()))
return switches.get(argument.name())[0];
SDVariable[] s = sameDiff.switchOp(argument, pred);
switches.put(argument.name(), s);
return s[0];
});
NameScope falseScope = sameDiff.withNameScope("falseBody");
SDVariable falseOut = falseBody.define(sameDiff);
sameDiff.removeArgumentInterceptor();
if(declared2.contains(falseOut.name())) {
SDVariable[] s = sameDiff.switchOp(falseOut, pred);
switches.put(falseOut.name(), s);
falseOut = s[0];
}
falseScope.close();
SDVariable output = sameDiff.merge(trueOut, falseOut);
ifScope.close();
return sameDiff.updateVariableNameAndReference(output, outputName);
}
@Builder
@Data
public static class LoopArgs {
private SDVariable condIn,maxIters,startIter;
private SDVariable[] extraArgs;
public SDVariable[] toArgs() {
SDVariable[] ret = new SDVariable[3 + extraArgs.length];
ret[0] = startIter;
ret[1] = maxIters;
ret[2] = condIn;
for(int i = 0; i < extraArgs.length; i++) {
ret[i + 3] = extraArgs[i];
}
return ret;
}
}
@Builder
@Data
public static class LoopParams {
private String[] outputVarNames;
private String loopName;
private SameDiff parent;
private SameDiff functionBody;
private String functionName;
private SDVariable[] loopVars;
private String[] functionBodyInputs;
private String[] functionBodyOutputs;
}
/**
* A simplified function using {@link LoopParams}
* invoking the same function as {@link #loopWithConditions(String[], String, SameDiff, SameDiff, String, SDVariable[], String[], String[])}
* @param loopParams the loop parameters to use
* @return
*/
public static SDVariable[] loopWithConditions(LoopParams loopParams) {
return loopWithConditions(loopParams.outputVarNames,
loopParams.loopName,loopParams.parent,
loopParams.functionBody,
loopParams.functionName,
loopParams.loopVars,
loopParams.functionBodyInputs,
loopParams.functionBodyOutputs);
}
/**
* Loop with conditions allows a user to provide a lambda to invoke
* any number of times.
* @param outputVarNames the output variable names to use
* @param loopName the name of the loop to use when creating the variables/ops
* @param parent the parent samediff instance to put the loop
* @param functionBody the function body to use
* @param functionName the name of the function to use within the samediff instance
* @param loopVars the loop variables to use during execution
* @param functionBodyInputs the inputs to invoke the function with
* @param functionBodyOutputs the outputs to be retrieved from the function itself
* @return the output exit variables at the end of the loop
*/
public static SDVariable[] loopWithConditions(
String[] outputVarNames,
String loopName,
SameDiff parent,
SameDiff functionBody,
String functionName,
SDVariable[] loopVars,
String[] functionBodyInputs,
String[] functionBodyOutputs) {
Preconditions.checkState(functionBodyInputs != null && functionBodyOutputs != null && functionBodyInputs.length == functionBodyOutputs.length,"Sub graph input and output names must be defined and equal in length.");
Preconditions.checkState(loopVars.length == functionBodyInputs.length,"Loop variables and function body inputs must be equal in length.");
for(SDVariable variable : loopVars) {
if(variable.getSameDiff() != parent) {
throw new IllegalArgumentException("Variable named " + variable.name() + " does not have correct samediff instance. Must have parent outer samediff instance.");
}
}
SameDiffSingleLambda cond = condBody();
SameDiffLambda loopBody = loopBody(parent,functionBody,functionName,functionBodyInputs,functionBodyOutputs,outputVarNames);
return parent.whileLoop(outputVarNames,loopName,loopVars,cond,loopBody);
}
/**
* Create {@link LoopLambdaArgs} from the given arguments.
* This is used to properly order arguments for use with {@link #loopBody(SameDiff, SameDiff, String, String[], String[])}
* and {@link #condBody()}
* @param inputs the inputs to order, these generally should be from within a lambda. The first 3 arguments are:
* current iter count, maximum number of iterations, extra arguments if any
* @return
*/
public static LoopLambdaArgs argsFromInputs(SDVariable[] inputs) {
SDVariable[] extraArgs = inputs.length > 3 ? new SDVariable[inputs.length - 3] : new SDVariable[0];
//add extra arguments offset by 3 representing custom inputs
if(extraArgs.length > 0) {
for(int i = 0; i < extraArgs.length; i++) {
extraArgs[i] = inputs[i + 3];
}
}
return LoopLambdaArgs.builder()
.iterCount(inputs[1])
.iterStart(inputs[0])
.condIn(inputs[2])
.extraArgs(extraArgs)
.build();
}
@Data
public static class LoopLambdaArgs {
private SDVariable iterStart;
private SDVariable iterCount;
private SDVariable condIn;
private SDVariable[] extraArgs;
@Builder
public LoopLambdaArgs(SDVariable iterStart,SDVariable iterCount,SDVariable[] extraArgs,SDVariable condIn) {
if(condIn.dataType() != DataType.BOOL) {
throw new IllegalArgumentException("Data type for condition must be boolean!");
}
if(!iterCount.dataType().isNumerical()) {
throw new IllegalArgumentException("Data type for condition must be numerical!");
}
this.iterCount = iterCount;
this.extraArgs = extraArgs;
this.condIn = condIn;
this.iterStart = iterStart;
}
/**
* Construct {@link org.nd4j.linalg.api.ops.custom.Invoke.InvokeParams}
* for usage with {@link SameDiff#invoke(Invoke.InvokeParams)}
* the variables here reflect what is used in the loop.
* A user can use {@link LoopLambdaArgs} to create an appropriately configured
* {@link org.nd4j.linalg.api.ops.custom.Invoke.InvokeParams} to be used
* with the body.
*
*
*
* @param functionName the name of the function to invoke
* @param subGraphInputNames the subgraph input names to invoke the function with
* @param subGraphOutputNames the subgraph output names to expect returned from the function
* @return the appropriate invoke parameters for use with {@link #condBody()} and {@link #loopBody(SameDiff, SameDiff, String, String[], String[])}
*/
public Invoke.InvokeParams invokeParams(String functionName,String[] subGraphInputNames,String[] subGraphOutputNames) {
return invokeParams(functionName, subGraphInputNames, subGraphOutputNames, null);
}
/**
* Construct {@link org.nd4j.linalg.api.ops.custom.Invoke.InvokeParams}
* for usage with {@link SameDiff#invoke(Invoke.InvokeParams)}
* the variables here reflect what is used in the loop.
* A user can use {@link LoopLambdaArgs} to create an appropriately configured
* {@link org.nd4j.linalg.api.ops.custom.Invoke.InvokeParams} to be used
* with the body.
*
* @param functionName the name of the function to invoke
* @param subGraphInputNames the subgraph input names to invoke the function with
* @param subGraphOutputNames the subgraph output names to expect returned from the function
* @param outputVarNames the output variable names for the parent graph
* @return the appropriate invoke parameters for use with {@link #condBody()} and {@link #loopBody(SameDiff, SameDiff, String, String[], String[])}
*/
public Invoke.InvokeParams invokeParams(String functionName, String[] subGraphInputNames, String[] subGraphOutputNames, String[] outputVarNames) {
List<SDVariable> inputs = new ArrayList<>();
//starting iteration
inputs.add(iterStart);
//ending iteration
inputs.add(iterCount);
//user custom condition
inputs.add(condIn);
inputs.addAll(Arrays.asList(extraArgs));
return Invoke.InvokeParams.builder()
.functionName(functionName)
.inputs(inputs.toArray(new SDVariable[inputs.size()]))
.subGraphInputVarNames(subGraphInputNames)
.subGraphOutputVarNames(subGraphOutputNames)
.inputVarNames(inputs.stream().map(input ->
input.name()).collect(Collectors.toList())
.toArray(new String[inputs.size()]))
.outputVarNames(outputVarNames)
.build();
}
}
/**
* Create a {@link SameDiffLambda} to be used in combination with
* {@link #condBody()} and {@link SameDiff#invoke(Invoke.InvokeParams)}
* this lambda will use samediff invoke as the function body
* and setup the appropriate parameters to create a looping construct
* as described in {@link #loopBody(SameDiff, SameDiff, String, String[], String[])}
* @param parent
* @param functionBody
* @param functionName
* @param subGraphInputNames the subgraph input names for use to invoke the graph with
* @param subGraphOutputNames the subgraph output names to expect to be returned from the subgraph invoke
* @return
*/
public static SameDiffLambda loopBody(SameDiff parent,
SameDiff functionBody,
String functionName,
String[] subGraphInputNames,
String[] subGraphOutputNames) {
return loopBody(parent, functionBody, functionName, subGraphInputNames, subGraphOutputNames, null);
}
/**
* Create a {@link SameDiffLambda} to be used in combination with
* {@link #condBody()} and {@link SameDiff#invoke(Invoke.InvokeParams)}
* this lambda will use samediff invoke as the function body
* and setup the appropriate parameters to create a looping construct
* as described in {@link #loopBody(SameDiff, SameDiff, String, String[], String[])}
* @param parent
* @param functionBody
* @param functionName
* @param subGraphInputNames the subgraph input names for use to invoke the graph with
* @param subGraphOutputNames the subgraph output names to expect to be returned from the subgraph invoke
* @param outputVarNames the output variable names for the parent graph
* @return
*/
public static SameDiffLambda loopBody(SameDiff parent,
SameDiff functionBody,
String functionName,
String[] subGraphInputNames,
String[] subGraphOutputNames,
String[] outputVarNames) {
Preconditions.checkState(subGraphInputNames != null && subGraphOutputNames != null && subGraphInputNames.length == subGraphOutputNames.length,"Sub graph input and output names must be defined and equal in length.");
if(parent.getFunction(functionName) == null)
parent.putSubFunction(functionName,functionBody);
return (sameDiff, inputs) -> {
LoopLambdaArgs loopLambdaArgs = ControlFlow.argsFromInputs(inputs);
Invoke.InvokeParams invokeParams = loopLambdaArgs.invokeParams(functionName, subGraphInputNames, subGraphOutputNames, outputVarNames);
SDVariable[] invoke = sameDiff.invoke(invokeParams);
List<SDVariable> retList = new ArrayList<>();
//current iterations + 1 (each time the body is invoked update the current iteration)
retList.add(inputs[0].add(1.0));
retList.add(inputs[1]);
retList.add(invoke[2]);
//assign extra parameters to the invoke output
//loop over non condition out variables starting from the end
for(int i = 3; i < invoke.length; i++) {
retList.add(invoke[i]);
}
return retList.toArray(new SDVariable[retList.size()]);
};
}
/**
* Constructs a While loop using the tensorflow style control flow operations (Switch, Merge, Enter, Exit, and NextIteration)
* <p>
* Repeatedly executes body on the loop variables and updates them with the results, until cond evaluates to false
* <p>
* Note that cond and body lambdas are only called once to construct the graph. The constructed graph is used for further iterations.
* <p>
* See <a href="http://download.tensorflow.org/paper/white_paper_tf_control_flow_implementation_2017_11_1.pdf">Tensorflow Control Flow Implementation</a>
*
* @param outputNames Names to give the output variables. If null, doesn't rename
* @param loopName The name of the loop block and frame (must be unique). If null, uses "if"
* @param loopVars Loop variables' inputs
* @param cond A lambda evaluating to the loop condition
* @param body A lambda doing the loop operation and returning the new loop variable values
* @return The values of the loop variables once condition is false
*/
public static SDVariable[] whileLoop(SameDiff sameDiff, String[] outputNames, final String loopName, @NonNull SDVariable[] loopVars,
@NonNull SameDiffSingleLambda cond, @NonNull SameDiffLambda body) {
final String frameName = sameDiff.newBlockName(loopName == null ? "while" : loopName);
NameScope loopScope = sameDiff.withNameScope(frameName);
SDVariable counter = sameDiff.scalar(sameDiff.generateNewVarName("counter", 0), 0);
SDVariable[] entered = new SDVariable[loopVars.length];
for (int i = 0; i < loopVars.length; i++) {
entered[i] = new Enter(sameDiff, frameName, loopVars[i]).outputVariable();
}
SDVariable[] merged = new SDVariable[loopVars.length];
Merge[] mergeOps = new Merge[loopVars.length];
for (int i = 0; i < loopVars.length; i++) {
// the second arg will later be replaced with the output of NextIteration
// but that isn't available yet (and can't be, as it depends on this)
mergeOps[i] = new Merge(sameDiff, entered[i], entered[i]);
mergeOps[i].setFrameName(frameName);
merged[i] = mergeOps[i].outputVariable();
}
Merge counterMerge = new Merge(sameDiff, counter, counter);
counter = counterMerge.outputVariable();
counterMerge.setFrameName(frameName);
NameScope condScope = sameDiff.withNameScope("cond");
SDVariable condResult = cond.define(sameDiff, merged);
condScope.close();
if (condResult.dataType() != DataType.BOOL)
throw new IllegalStateException("Can not use " + condResult.name() + " as the condition of an While loop, the condition must be a boolean.");
final Set<String> alreadyEntered = Sets.newHashSet();
SDVariable[] trueSwitches = new SDVariable[loopVars.length];
SDVariable[] exits = new SDVariable[loopVars.length];
for (int i = 0; i < loopVars.length; i++) {
SDVariable[] s = sameDiff.switchOp(merged[i], condResult);
trueSwitches[i] = s[1];
alreadyEntered.add(s[1].name());
Exit exit = new Exit(sameDiff, s[0]);
exit.setFrameName(frameName);
exits[i] = exit.outputVariable();
}
final Set<String> declared = Sets.newHashSet(sameDiff.variableMap().keySet());
final Map<String, SDVariable> done = new HashMap<>();
final SameDiff sd = sameDiff;
sameDiff.addArgumentInterceptor(argument -> {
if (argument == null)
return null;
if (!declared.contains(argument.name()))
return argument;
if (alreadyEntered.contains(argument.name()))
return argument;
if (done.containsKey(argument.name()))
return done.get(argument.name());
SDVariable e = new Enter(sd, frameName, argument, true).outputVariable();
done.put(argument.name(), e);
return e;
});
NameScope bodyScope = sameDiff.withNameScope("body");
SDVariable[] outs = body.define(sameDiff, trueSwitches);
if (outs.length != mergeOps.length)
throw new IllegalArgumentException("Number of loop variables must be equal to number of outputs.");
bodyScope.close();
sameDiff.removeArgumentInterceptor();
counter.add(1);
for (int i = 0; i < outs.length; i++) {
NextIteration nextIteration = new NextIteration(sameDiff, outs[i]);
nextIteration.setFrameName(frameName);
SDVariable n = nextIteration.outputVariable();
mergeOps[i].replaceArg(1, n);
}
counterMerge.replaceArg(1, counter);
loopScope.close();
return sameDiff.updateVariableNamesAndReferences(exits, outputNames);
}
/**
* Returns a lambda that takes in a custom condition and a built-in for
* loop counter concept in the following manner:
* int currIteration = 0;
* boolean cond = ...;
* int maxIterations = ...;
* for(int i = currIteration; i < maxIterations && cond; i++) {
* //body....
* }
*
* The inputs to the lambda are the following order:
* currIteration (the starting iteration)
* maxIterations (the number of times to loop)
* cond: the custom condition the user passes in
*
*
* @return the lambda described above for usage in the {@link #whileLoop(SameDiff, String[], String, SDVariable[], SameDiffSingleLambda, SameDiffLambda)}
* routine
*/
public static SameDiffSingleLambda condBody() {
// combine for loop and while loop together
return (sameDiff, inputs) -> {
SDVariable currIteration = inputs[0];
SDVariable maxIterations = inputs[1];
SDVariable extraCond = inputs[2];
SDVariable and = sameDiff.bitwise().and(
currIteration.lt(maxIterations.castTo(currIteration.dataType()))
.castTo(DataType.INT64),
extraCond.castTo(DataType.INT64));
SDVariable ret = and.castTo( DataType.BOOL);
return ret;
};
}
}
@@ -0,0 +1,46 @@
/*
* ******************************************************************************
* *
* *
* * This program and the accompanying materials are made available under the
* * terms of the Apache License, Version 2.0 which is available at
* * https://www.apache.org/licenses/LICENSE-2.0.
* *
* * See the NOTICE file distributed with this work for additional
* * information regarding copyright ownership.
* * Unless required by applicable law or agreed to in writing, software
* * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* * License for the specific language governing permissions and limitations
* * under the License.
* *
* * SPDX-License-Identifier: Apache-2.0
* *****************************************************************************
*/
package org.nd4j.autodiff.samediff;
/**
* Cross-frame variable reference information
*/
public class CrossFrameReference {
public String variableName;
public String sourceFrame;
public String targetFrame;
public int sourceIteration;
public int targetIteration;
public String mediatingOperation;
public CrossFrameReferenceType referenceType;
public CrossFrameReference() {
// Default constructor
}
public CrossFrameReference(String variableName, String sourceFrame, String targetFrame,
CrossFrameReferenceType referenceType) {
this.variableName = variableName;
this.sourceFrame = sourceFrame;
this.targetFrame = targetFrame;
this.referenceType = referenceType;
}
}
@@ -0,0 +1,32 @@
/*
* ******************************************************************************
* *
* *
* * This program and the accompanying materials are made available under the
* * terms of the Apache License, Version 2.0 which is available at
* * https://www.apache.org/licenses/LICENSE-2.0.
* *
* * See the NOTICE file distributed with this work for additional
* * information regarding copyright ownership.
* * Unless required by applicable law or agreed to in writing, software
* * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* * License for the specific language governing permissions and limitations
* * under the License.
* *
* * SPDX-License-Identifier: Apache-2.0
* *****************************************************************************
*/
package org.nd4j.autodiff.samediff;
/**
* Types of cross-frame variable references
*/
public enum CrossFrameReferenceType {
DIRECT, // Direct variable reference
ENTER, // Variable entering frame
EXIT, // Variable exiting frame
LOOP_CARRIED, // Variable carried across loop iterations
CONDITIONAL // Variable from conditional branch
}
@@ -0,0 +1,16 @@
package org.nd4j.autodiff.samediff;
import lombok.Data;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Data
public class CrossLoopAnalysis {
private Map<TerminationType, Long> terminationTypeDistribution = new HashMap<>();
private List<String> terminationCorrelations = new ArrayList<>();
private List<String> systemWideIssues = new ArrayList<>();
private Map<String, Integer> commonProblematicVariables = new HashMap<>();
}
@@ -0,0 +1,853 @@
/*
* ******************************************************************************
* *
* *
* * This program and the accompanying materials are made available under the
* * terms of the Apache License, Version 2.0 which is available at
* * https://www.apache.org/licenses/LICENSE-2.0.
* *
* * See the NOTICE file distributed with this work for additional
* * information regarding copyright ownership.
* * Unless required by applicable law or agreed to in writing, software
* * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* * License for the specific language governing permissions and limitations
* * under the License.
* *
* * SPDX-License-Identifier: Apache-2.0
* *****************************************************************************
*/
package org.nd4j.autodiff.samediff;
import org.nd4j.linalg.api.buffer.DataType;
import java.util.*;
import java.util.stream.Collectors;
/**
* Class to hold the results of a dry run execution analysis with enhanced frame granularity
*/
public class DAGExecutionPlan {
// Basic execution plan data
private List<String> executionOrder = new ArrayList<>();
private Map<String, Set<String>> dependencies = new HashMap<>();
private List<String> requestedOutputs = new ArrayList<>();
private Map<String, VariableInfo> variables = new HashMap<>();
private Map<String, OperationInfo> operations = new HashMap<>();
private Set<String> leafVariables = new HashSet<>();
private Set<String> trainableVariables = new HashSet<>();
private Set<String> sequenceVariables = new HashSet<>();
private List<String> missingVariables = new ArrayList<>();
private List<String> orphanedVariables = new ArrayList<>();
private List<String> cycles = new ArrayList<>();
private Map<String, List<String>> controlDependencies = new HashMap<>();
private Map<String, List<String>> variableControlDependencies = new HashMap<>();
// Enhanced frame management structures
private Set<String> enterOperations = new HashSet<>();
private Set<String> exitOperations = new HashSet<>();
private Set<String> switchOperations = new HashSet<>();
private Set<String> mergeOperations = new HashSet<>();
private Set<String> nextIterationOperations = new HashSet<>();
private Set<String> loopConditionOperations = new HashSet<>();
// Frame hierarchy and relationships
private Map<String, String> frameHierarchy = new HashMap<>(); // frame -> parent frame
private Map<String, Set<String>> framesUsed = new HashMap<>(); // frame -> operations in frame
private Map<String, Set<String>> frameChildren = new HashMap<>(); // frame -> child frames
private Map<String, Integer> frameDepth = new HashMap<>(); // frame -> nesting depth
private Map<String, FrameMetadata> frameMetadata = new HashMap<>(); // frame -> detailed metadata
// Frame execution analysis
private Map<String, List<String>> frameExecutionOrder = new HashMap<>(); // frame -> ops in execution order
private Map<String, Set<String>> frameVariables = new HashMap<>(); // frame -> variables in frame
private Map<String, Set<String>> frameBoundaryOperations = new HashMap<>(); // frame -> boundary ops
private Map<String, Map<String, Integer>> frameIterationCounts = new HashMap<>(); // frame -> var -> max iteration
// Frame transitions and dependencies
private Map<String, List<FrameTransitionInfo>> frameTransitions = new HashMap<>(); // from frame -> transitions
private Map<String, Set<String>> frameDependencies = new HashMap<>(); // frame -> dependent frames
private Map<String, Set<String>> frameProducers = new HashMap<>(); // frame -> frames that produce data for this frame
private Map<String, Set<String>> frameConsumers = new HashMap<>(); // frame -> frames that consume data from this frame
// Cross-frame data flow
private Map<String, List<CrossFrameReference>> crossFrameReferences = new HashMap<>(); // var -> cross-frame refs
private Map<String, Set<String>> frameInputVariables = new HashMap<>(); // frame -> input variables from other frames
private Map<String, Set<String>> frameOutputVariables = new HashMap<>(); // frame -> output variables to other frames
// Basic getters and setters
public List<String> getExecutionOrder() {
return executionOrder;
}
public void setExecutionOrder(List<String> executionOrder) {
this.executionOrder = executionOrder;
}
public Map<String, Set<String>> getDependencies() {
return dependencies;
}
public void setDependencies(Map<String, Set<String>> dependencies) {
this.dependencies = dependencies;
}
public List<String> getRequestedOutputs() {
return requestedOutputs;
}
public void setRequestedOutputs(List<String> requestedOutputs) {
this.requestedOutputs = requestedOutputs;
}
// Frame management methods
/**
* Add a frame with detailed metadata
*/
public void addFrame(String frameName, String parentFrame, FrameType type) {
int depth = parentFrame == null ? 0 : frameDepth.getOrDefault(parentFrame, 0) + 1;
FrameMetadata metadata = new FrameMetadata(frameName, parentFrame, depth, type);
frameMetadata.put(frameName, metadata);
frameDepth.put(frameName, depth);
if (parentFrame != null) {
frameHierarchy.put(frameName, parentFrame);
frameChildren.computeIfAbsent(parentFrame, k -> new HashSet<>()).add(frameName);
frameMetadata.get(parentFrame).childFrames.add(frameName);
}
framesUsed.computeIfAbsent(frameName, k -> new HashSet<>());
frameExecutionOrder.computeIfAbsent(frameName, k -> new ArrayList<>());
frameVariables.computeIfAbsent(frameName, k -> new HashSet<>());
frameBoundaryOperations.computeIfAbsent(frameName, k -> new HashSet<>());
frameIterationCounts.computeIfAbsent(frameName, k -> new HashMap<>());
}
/**
* Add frame transition information
*/
public void addFrameTransition(String fromFrame, String toFrame, FrameTransition transitionType,
String operationName, List<String> affectedVariables, int iterationChange) {
FrameTransitionInfo transition = new FrameTransitionInfo(fromFrame, toFrame, transitionType, operationName);
if (affectedVariables != null) {
transition.affectedVariables.addAll(affectedVariables);
}
transition.iterationChange = iterationChange;
frameTransitions.computeIfAbsent(fromFrame, k -> new ArrayList<>()).add(transition);
// Update frame dependencies
if (toFrame != null && !fromFrame.equals(toFrame)) {
frameDependencies.computeIfAbsent(toFrame, k -> new HashSet<>()).add(fromFrame);
frameProducers.computeIfAbsent(toFrame, k -> new HashSet<>()).add(fromFrame);
frameConsumers.computeIfAbsent(fromFrame, k -> new HashSet<>()).add(toFrame);
}
// Update metadata
FrameMetadata fromMeta = frameMetadata.get(fromFrame);
if (fromMeta != null) {
fromMeta.transitionCounts.merge(transitionType, 1, Integer::sum);
if (transitionType == FrameTransition.NEXT_ITERATION) {
fromMeta.hasLoops = true;
} else if (transitionType == FrameTransition.SWITCH) {
fromMeta.hasConditionals = true;
}
}
}
/**
* Add cross-frame variable reference
*/
public void addCrossFrameReference(String variableName, String sourceFrame, String targetFrame,
int sourceIteration, int targetIteration, String mediatingOperation,
CrossFrameReferenceType type) {
CrossFrameReference ref = new CrossFrameReference();
ref.variableName = variableName;
ref.sourceFrame = sourceFrame;
ref.targetFrame = targetFrame;
ref.sourceIteration = sourceIteration;
ref.targetIteration = targetIteration;
ref.mediatingOperation = mediatingOperation;
ref.referenceType = type;
crossFrameReferences.computeIfAbsent(variableName, k -> new ArrayList<>()).add(ref);
// Update frame input/output tracking
if (targetFrame != null) {
frameInputVariables.computeIfAbsent(targetFrame, k -> new HashSet<>()).add(variableName);
}
if (sourceFrame != null) {
frameOutputVariables.computeIfAbsent(sourceFrame, k -> new HashSet<>()).add(variableName);
}
}
public void addFrameOperation(String opName, FrameTransition transition, String frame, String parentFrame) {
switch (transition) {
case ENTER:
enterOperations.add(opName);
if (frame != null) {
frameBoundaryOperations.computeIfAbsent(frame, k -> new HashSet<>()).add(opName);
FrameMetadata meta = frameMetadata.get(frame);
if (meta != null) {
meta.entryPoints.add(opName);
}
}
break;
case EXIT:
exitOperations.add(opName);
if (frame != null) {
frameBoundaryOperations.computeIfAbsent(frame, k -> new HashSet<>()).add(opName);
FrameMetadata meta = frameMetadata.get(frame);
if (meta != null) {
meta.exitPoints.add(opName);
}
}
break;
case SWITCH:
switchOperations.add(opName);
break;
case MERGE:
mergeOperations.add(opName);
break;
case NEXT_ITERATION:
nextIterationOperations.add(opName);
break;
case LOOP_CONDITION:
loopConditionOperations.add(opName);
break;
}
if (parentFrame != null && frame != null) {
frameHierarchy.put(frame, parentFrame);
}
}
public void addOperationWithFrame(String opName, String opType, String className,
List<String> inputs, List<String> outputs, FrameInfo frameInfo) {
OperationInfo opInfo = new OperationInfo(opName, opType, className, inputs, outputs);
opInfo.setFrameInfo(frameInfo);
operations.put(opName, opInfo);
// Track frame usage and execution order
String frame = frameInfo.outputFrame;
if (frame != null) {
framesUsed.computeIfAbsent(frame, k -> new HashSet<>()).add(opName);
frameExecutionOrder.computeIfAbsent(frame, k -> new ArrayList<>()).add(opName);
// Update frame metadata
FrameMetadata meta = frameMetadata.get(frame);
if (meta != null) {
meta.totalOperations++;
}
}
}
public void addVariableWithFrame(String name, VariableType type, DataType dataType,
String frame, int iteration, String parentFrame) {
VariableInfo varInfo = new VariableInfo(name, type, dataType);
varInfo.frame = frame;
varInfo.iteration = iteration;
varInfo.parentFrame = parentFrame;
variables.put(name, varInfo);
// Track frame variables and iterations
if (frame != null) {
frameVariables.computeIfAbsent(frame, k -> new HashSet<>()).add(name);
frameIterationCounts.computeIfAbsent(frame, k -> new HashMap<>())
.merge(name, iteration, Integer::max);
// Update frame metadata
FrameMetadata meta = frameMetadata.get(frame);
if (meta != null) {
meta.totalVariables++;
meta.maxIterations = Math.max(meta.maxIterations, iteration);
}
}
}
// Production utility methods
/**
* Get all operations within a specific frame
*/
public List<String> getOperationsInFrame(String frameName) {
return frameExecutionOrder.getOrDefault(frameName, Collections.emptyList());
}
/**
* Get all variables within a specific frame
*/
public Set<String> getVariablesInFrame(String frameName) {
return frameVariables.getOrDefault(frameName, Collections.emptySet());
}
/**
* Get frame for a specific operation
*/
public String getOperationFrame(String operationName) {
OperationInfo opInfo = operations.get(operationName);
if (opInfo != null && opInfo.getFrameInfo() != null) {
return opInfo.getFrameInfo().outputFrame;
}
return null;
}
/**
* Get frame for a specific variable
*/
public String getVariableFrame(String variableName) {
VariableInfo varInfo = variables.get(variableName);
if (varInfo != null) {
return varInfo.frame;
}
return null;
}
/**
* Find all variables that are shared between frames
*/
public Map<String, List<String>> getSharedVariables() {
Map<String, List<String>> sharedVars = new HashMap<>();
Map<String, Set<String>> varToFrames = new HashMap<>();
// Build mapping of variables to frames
for (Map.Entry<String, Set<String>> entry : frameVariables.entrySet()) {
String frame = entry.getKey();
for (String var : entry.getValue()) {
varToFrames.computeIfAbsent(var, k -> new HashSet<>()).add(frame);
}
}
// Find variables in multiple frames
for (Map.Entry<String, Set<String>> entry : varToFrames.entrySet()) {
if (entry.getValue().size() > 1) {
sharedVars.put(entry.getKey(), new ArrayList<>(entry.getValue()));
}
}
return sharedVars;
}
/**
* Get operations that produce outputs for a specific frame
*/
public List<String> getFrameProducerOperations(String frameName) {
Set<String> producers = frameProducers.getOrDefault(frameName, Collections.emptySet());
List<String> producerOps = new ArrayList<>();
for (String producerFrame : producers) {
producerOps.addAll(getOperationsInFrame(producerFrame));
}
return producerOps;
}
/**
* Get operations that consume outputs from a specific frame
*/
public List<String> getFrameConsumerOperations(String frameName) {
Set<String> consumers = frameConsumers.getOrDefault(frameName, Collections.emptySet());
List<String> consumerOps = new ArrayList<>();
for (String consumerFrame : consumers) {
consumerOps.addAll(getOperationsInFrame(consumerFrame));
}
return consumerOps;
}
/**
* Get all child frames for a given parent frame
*/
public Set<String> getChildFrames(String parentFrame) {
return frameChildren.getOrDefault(parentFrame, Collections.emptySet());
}
/**
* Get parent frame for a given frame
*/
public String getParentFrame(String frameName) {
return frameHierarchy.get(frameName);
}
/**
* Check if a frame contains loops
*/
public boolean frameHasLoops(String frameName) {
FrameMetadata meta = frameMetadata.get(frameName);
return meta != null && meta.hasLoops;
}
/**
* Check if a frame contains conditionals
*/
public boolean frameHasConditionals(String frameName) {
FrameMetadata meta = frameMetadata.get(frameName);
return meta != null && meta.hasConditionals;
}
/**
* Get maximum iteration count for variables in a frame
*/
public int getFrameMaxIterations(String frameName) {
FrameMetadata meta = frameMetadata.get(frameName);
return meta != null ? meta.maxIterations : 0;
}
/**
* Get all frames that this frame depends on (transitive closure)
*/
public Set<String> getAllFrameDependencies(String frameName) {
Set<String> allDeps = new HashSet<>();
collectFrameDependencies(frameName, allDeps, new HashSet<>());
return allDeps;
}
/**
* Get operations by frame transition type
*/
public Set<String> getOperationsByTransition(FrameTransition transitionType) {
switch (transitionType) {
case ENTER: return enterOperations;
case EXIT: return exitOperations;
case SWITCH: return switchOperations;
case MERGE: return mergeOperations;
case NEXT_ITERATION: return nextIterationOperations;
case LOOP_CONDITION: return loopConditionOperations;
default: return Collections.emptySet();
}
}
/**
* Find operations that execute before entering a specific frame
*/
public List<String> getPreFrameOperations(String frameName) {
List<String> preOps = new ArrayList<>();
FrameMetadata meta = frameMetadata.get(frameName);
if (meta != null && !meta.entryPoints.isEmpty()) {
String firstEntryOp = meta.entryPoints.get(0);
int entryIndex = executionOrder.indexOf(firstEntryOp);
if (entryIndex > 0) {
for (int i = 0; i < entryIndex; i++) {
String opName = executionOrder.get(i);
String opFrame = getOperationFrame(opName);
if (opFrame == null || !opFrame.equals(frameName)) {
preOps.add(opName);
}
}
}
}
return preOps;
}
/**
* Find operations that execute after exiting a specific frame
*/
public List<String> getPostFrameOperations(String frameName) {
List<String> postOps = new ArrayList<>();
FrameMetadata meta = frameMetadata.get(frameName);
if (meta != null && !meta.exitPoints.isEmpty()) {
String lastExitOp = meta.exitPoints.get(meta.exitPoints.size() - 1);
int exitIndex = executionOrder.indexOf(lastExitOp);
if (exitIndex >= 0 && exitIndex < executionOrder.size() - 1) {
for (int i = exitIndex + 1; i < executionOrder.size(); i++) {
String opName = executionOrder.get(i);
String opFrame = getOperationFrame(opName);
if (opFrame == null || !opFrame.equals(frameName)) {
postOps.add(opName);
}
}
}
}
return postOps;
}
// Analysis methods
/**
* Get all frames at a specific nesting depth
*/
public Set<String> getFramesAtDepth(int depth) {
return frameDepth.entrySet().stream()
.filter(e -> e.getValue() == depth)
.map(Map.Entry::getKey)
.collect(Collectors.toSet());
}
/**
* Get the execution path through frames
*/
public List<String> getFrameExecutionPath() {
List<String> path = new ArrayList<>();
Map<String, Integer> frameFirstOp = new HashMap<>();
// Find first operation in each frame
for (int i = 0; i < executionOrder.size(); i++) {
String opName = executionOrder.get(i);
OperationInfo opInfo = operations.get(opName);
if (opInfo != null && opInfo.getFrameInfo() != null && opInfo.getFrameInfo().outputFrame != null) {
String frame = opInfo.getFrameInfo().outputFrame;
if (!frameFirstOp.containsKey(frame)) {
frameFirstOp.put(frame, i);
}
}
}
// Sort frames by first operation order
return frameFirstOp.entrySet().stream()
.sorted(Map.Entry.comparingByValue())
.map(Map.Entry::getKey)
.collect(Collectors.toList());
}
/**
* Analyze frame dependencies and detect circular dependencies
*/
public Map<String, Set<String>> analyzeFrameDependencies() {
Map<String, Set<String>> result = new HashMap<>();
for (String frame : frameMetadata.keySet()) {
Set<String> allDeps = new HashSet<>();
collectFrameDependencies(frame, allDeps, new HashSet<>());
result.put(frame, allDeps);
}
return result;
}
private void collectFrameDependencies(String frame, Set<String> allDeps, Set<String> visited) {
if (visited.contains(frame)) {
// Circular dependency detected
cycles.add("Frame circular dependency involving: " + frame);
return;
}
visited.add(frame);
Set<String> directDeps = frameDependencies.getOrDefault(frame, Collections.emptySet());
allDeps.addAll(directDeps);
for (String dep : directDeps) {
collectFrameDependencies(dep, allDeps, visited);
}
visited.remove(frame);
}
/**
* Get detailed frame statistics
*/
public Map<String, Object> getDetailedFrameStats() {
Map<String, Object> stats = new HashMap<>();
stats.put("totalFrames", frameMetadata.size());
stats.put("maxFrameDepth", frameDepth.values().stream().mapToInt(Integer::intValue).max().orElse(0));
stats.put("framesWithLoops", frameMetadata.values().stream().mapToLong(m -> m.hasLoops ? 1 : 0).sum());
stats.put("framesWithConditionals", frameMetadata.values().stream().mapToLong(m -> m.hasConditionals ? 1 : 0).sum());
stats.put("totalCrossFrameReferences", crossFrameReferences.values().stream().mapToInt(List::size).sum());
// Frame type distribution
Map<FrameType, Long> typeDistribution = frameMetadata.values().stream()
.collect(Collectors.groupingBy(m -> m.frameType, Collectors.counting()));
stats.put("frameTypeDistribution", typeDistribution);
// Transition statistics
Map<FrameTransition, Integer> totalTransitions = new HashMap<>();
for (FrameMetadata meta : frameMetadata.values()) {
for (Map.Entry<FrameTransition, Integer> entry : meta.transitionCounts.entrySet()) {
totalTransitions.merge(entry.getKey(), entry.getValue(), Integer::sum);
}
}
stats.put("frameTransitions", totalTransitions);
return stats;
}
// Legacy methods (maintained for compatibility)
public void addVariable(String name, VariableType type, DataType dataType) {
variables.put(name, new VariableInfo(name, type, dataType));
}
public void addOperation(String opName, String opType, String className, List<String> inputs, List<String> outputs) {
operations.put(opName, new OperationInfo(opName, opType, className, inputs, outputs));
}
public void addLeafVariable(String name) {
leafVariables.add(name);
}
public void addTrainableVariable(String name) {
trainableVariables.add(name);
}
public void addSequenceVariable(String name) {
sequenceVariables.add(name);
}
public void addMissingVariable(String name) {
missingVariables.add(name);
}
public void addOrphanedVariable(String varName, String missingOp) {
orphanedVariables.add(varName + (missingOp != null ? " (op: " + missingOp + ")" : ""));
}
public void addCycle(String name) {
cycles.add(name);
}
public void addControlDependencies(String opName, List<String> deps) {
controlDependencies.put(opName, new ArrayList<>(deps));
}
public void addVariableControlDependencies(String opName, List<String> deps) {
variableControlDependencies.put(opName, new ArrayList<>(deps));
}
public boolean isOperationProcessed(String opName) {
return operations.containsKey(opName);
}
/**
* Enhanced format summary with detailed frame information
*/
public String formatSummary() {
StringBuilder sb = new StringBuilder();
sb.append("=== SameDiff Execution DAG Analysis ===\n\n");
// Basic summary statistics
sb.append("Summary:\n");
sb.append(String.format(" Requested outputs: %d\n", requestedOutputs.size()));
sb.append(String.format(" Total operations: %d\n", operations.size()));
sb.append(String.format(" Total variables: %d\n", variables.size()));
sb.append(String.format(" Leaf variables: %d\n", leafVariables.size()));
sb.append(String.format(" Trainable variables: %d\n", trainableVariables.size()));
// Enhanced frame analysis
sb.append("\nFrame Analysis:\n");
sb.append(String.format(" Total frames: %d\n", frameMetadata.size()));
sb.append(String.format(" Max frame depth: %d\n", frameDepth.values().stream().mapToInt(Integer::intValue).max().orElse(0)));
sb.append(String.format(" Frames with loops: %d\n", frameMetadata.values().stream().mapToLong(m -> m.hasLoops ? 1 : 0).sum()));
sb.append(String.format(" Frames with conditionals: %d\n", frameMetadata.values().stream().mapToLong(m -> m.hasConditionals ? 1 : 0).sum()));
sb.append(String.format(" Cross-frame references: %d\n", crossFrameReferences.values().stream().mapToInt(List::size).sum()));
// Frame hierarchy
if (!frameMetadata.isEmpty()) {
sb.append("\nFrame Hierarchy:\n");
for (String rootFrame : getFramesAtDepth(0)) {
printFrameHierarchy(sb, rootFrame, 0);
}
}
// Frame execution path
List<String> framePath = getFrameExecutionPath();
if (!framePath.isEmpty()) {
sb.append("\nFrame Execution Path:\n");
for (int i = 0; i < framePath.size(); i++) {
String frame = framePath.get(i);
FrameMetadata meta = frameMetadata.get(frame);
sb.append(String.format(" %d. %s (%s) - %d ops, %d vars\n",
i + 1, frame, meta.frameType, meta.totalOperations, meta.totalVariables));
}
}
// Issues
if (!missingVariables.isEmpty() || !orphanedVariables.isEmpty() || !cycles.isEmpty()) {
sb.append("\nISSUES DETECTED:\n");
if (!missingVariables.isEmpty()) {
sb.append(String.format(" Missing variables: %s\n", missingVariables));
}
if (!orphanedVariables.isEmpty()) {
sb.append(String.format(" Orphaned variables: %s\n", orphanedVariables));
}
if (!cycles.isEmpty()) {
sb.append(String.format(" Potential cycles: %s\n", cycles));
}
}
return sb.toString();
}
private void printFrameHierarchy(StringBuilder sb, String frameName, int depth) {
FrameMetadata meta = frameMetadata.get(frameName);
if (meta == null) return;
String indentStr = " ".repeat(depth);
sb.append(String.format("%s- %s (%s): %d ops, %d vars, max iter: %d\n",
indentStr, frameName, meta.frameType, meta.totalOperations, meta.totalVariables, meta.maxIterations));
for (String child : meta.childFrames) {
printFrameHierarchy(sb, child, depth + 1);
}
}
/**
* Get control flow statistics (enhanced)
*/
public Map<String, Integer> getControlFlowStats() {
Map<String, Integer> stats = new HashMap<>();
stats.put("enterOps", enterOperations.size());
stats.put("exitOps", exitOperations.size());
stats.put("switchOps", switchOperations.size());
stats.put("mergeOps", mergeOperations.size());
stats.put("nextIterationOps", nextIterationOperations.size());
stats.put("loopConditionOps", loopConditionOperations.size());
stats.put("totalFrames", framesUsed.size());
stats.put("maxFrameDepth", frameDepth.values().stream().mapToInt(Integer::intValue).max().orElse(0));
return stats;
}
/**
* Get operations that directly produce the requested outputs
*/
public List<String> getOutputProducingOperations() {
List<String> result = new ArrayList<>();
for (String output : requestedOutputs) {
for (OperationInfo op : operations.values()) {
if (op.getOutputs() != null && op.getOutputs().contains(output)) {
result.add(op.getOperationName());
break;
}
}
}
return result;
}
/**
* Get the depth of the execution DAG (longest path)
*/
public int getExecutionDepth() {
return executionOrder.size();
}
// Getters for frame-related data structures
public Map<String, FrameMetadata> getFrameMetadata() {
return Collections.unmodifiableMap(frameMetadata);
}
public Map<String, List<CrossFrameReference>> getCrossFrameReferences() {
return Collections.unmodifiableMap(crossFrameReferences);
}
public Map<String, List<FrameTransitionInfo>> getFrameTransitions() {
return Collections.unmodifiableMap(frameTransitions);
}
public Map<String, Set<String>> getFrameDependencies() {
return Collections.unmodifiableMap(frameDependencies);
}
public Map<String, List<String>> getFrameExecutionOrder() {
return Collections.unmodifiableMap(frameExecutionOrder);
}
public Map<String, Set<String>> getFrameInputVariables() {
return Collections.unmodifiableMap(frameInputVariables);
}
public Map<String, Set<String>> getFrameOutputVariables() {
return Collections.unmodifiableMap(frameOutputVariables);
}
public Map<String, Set<String>> getFrameBoundaryOperations() {
return Collections.unmodifiableMap(frameBoundaryOperations);
}
public Map<String, Set<String>> getFrameProducers() {
return Collections.unmodifiableMap(frameProducers);
}
public Map<String, Set<String>> getFrameConsumers() {
return Collections.unmodifiableMap(frameConsumers);
}
public Map<String, Integer> getFrameDepth() {
return Collections.unmodifiableMap(frameDepth);
}
public Map<String, String> getFrameHierarchy() {
return Collections.unmodifiableMap(frameHierarchy);
}
public Map<String, Set<String>> getFrameChildren() {
return Collections.unmodifiableMap(frameChildren);
}
public Map<String, Map<String, Integer>> getFrameIterationCounts() {
return Collections.unmodifiableMap(frameIterationCounts);
}
public Set<String> getEnterOperations() {
return Collections.unmodifiableSet(enterOperations);
}
public Set<String> getExitOperations() {
return Collections.unmodifiableSet(exitOperations);
}
public Set<String> getSwitchOperations() {
return Collections.unmodifiableSet(switchOperations);
}
public Set<String> getMergeOperations() {
return Collections.unmodifiableSet(mergeOperations);
}
public Set<String> getNextIterationOperations() {
return Collections.unmodifiableSet(nextIterationOperations);
}
public Set<String> getLoopConditionOperations() {
return Collections.unmodifiableSet(loopConditionOperations);
}
public Map<String, VariableInfo> getVariables() {
return Collections.unmodifiableMap(variables);
}
public Map<String, OperationInfo> getOperations() {
return Collections.unmodifiableMap(operations);
}
public Set<String> getLeafVariables() {
return Collections.unmodifiableSet(leafVariables);
}
public Set<String> getTrainableVariables() {
return Collections.unmodifiableSet(trainableVariables);
}
public Set<String> getSequenceVariables() {
return Collections.unmodifiableSet(sequenceVariables);
}
public List<String> getMissingVariables() {
return Collections.unmodifiableList(missingVariables);
}
public List<String> getOrphanedVariables() {
return Collections.unmodifiableList(orphanedVariables);
}
public List<String> getCycles() {
return Collections.unmodifiableList(cycles);
}
public Map<String, List<String>> getControlDependencies() {
return Collections.unmodifiableMap(controlDependencies);
}
public Map<String, List<String>> getVariableControlDependencies() {
return Collections.unmodifiableMap(variableControlDependencies);
}
}
@@ -0,0 +1,129 @@
/*
* ******************************************************************************
* *
* *
* * This program and the accompanying materials are made available under the
* * terms of the Apache License, Version 2.0 which is available at
* * https://www.apache.org/licenses/LICENSE-2.0.
* *
* * See the NOTICE file distributed with this work for additional
* * information regarding copyright ownership.
* * Unless required by applicable law or agreed to in writing, software
* * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* * License for the specific language governing permissions and limitations
* * under the License.
* *
* * SPDX-License-Identifier: Apache-2.0
* *****************************************************************************
*/
package org.nd4j.autodiff.samediff;
import lombok.Data;
import java.util.*;
/**
* Entity classification result for execution failure analysis.
* Contains counts and lists of different entity types that are missing.
*/
@Data
public class EntityClassificationResult {
private final Set<String> operations = new LinkedHashSet<>();
private final Set<String> variables = new LinkedHashSet<>();
private final Set<String> constants = new LinkedHashSet<>();
private final Set<String> placeholders = new LinkedHashSet<>();
private final Set<String> unknown = new LinkedHashSet<>();
/**
* Add an entity to the appropriate category
*/
public void addEntity(String entityName, EntityType type) {
switch (type) {
case OPERATION:
operations.add(entityName);
break;
case VARIABLE:
variables.add(entityName);
break;
case CONSTANT:
constants.add(entityName);
break;
case PLACEHOLDER:
placeholders.add(entityName);
break;
case UNKNOWN:
unknown.add(entityName);
break;
}
}
/**
* Get total count of all missing entities
*/
public int getTotalCount() {
return operations.size() + variables.size() + constants.size() +
placeholders.size() + unknown.size();
}
/**
* Check if any variables are missing (indicates potential execution framework issue)
*/
public boolean hasVariables() {
return !variables.isEmpty();
}
/**
* Check if any legitimate operations are missing
*/
public boolean hasOperations() {
return !operations.isEmpty();
}
/**
* Check if there are any unknown entities
*/
public boolean hasUnknown() {
return !unknown.isEmpty();
}
/**
* Get count for a specific entity type
*/
public int getCountForType(EntityType type) {
switch (type) {
case OPERATION: return operations.size();
case VARIABLE: return variables.size();
case CONSTANT: return constants.size();
case PLACEHOLDER: return placeholders.size();
case UNKNOWN: return unknown.size();
default: return 0;
}
}
/**
* Get entities for a specific type
*/
public Set<String> getEntitiesForType(EntityType type) {
switch (type) {
case OPERATION: return new LinkedHashSet<>(operations);
case VARIABLE: return new LinkedHashSet<>(variables);
case CONSTANT: return new LinkedHashSet<>(constants);
case PLACEHOLDER: return new LinkedHashSet<>(placeholders);
case UNKNOWN: return new LinkedHashSet<>(unknown);
default: return new LinkedHashSet<>();
}
}
/**
* Clear all classifications
*/
public void clear() {
operations.clear();
variables.clear();
constants.clear();
placeholders.clear();
unknown.clear();
}
}
@@ -0,0 +1,140 @@
/*
* ******************************************************************************
* *
* *
* * This program and the accompanying materials are made available under the
* * terms of the Apache License, Version 2.0 which is available at
* * https://www.apache.org/licenses/LICENSE-2.0.
* *
* * See the NOTICE file distributed with this work for additional
* * information regarding copyright ownership.
* * Unless required by applicable law or agreed to in writing, software
* * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* * License for the specific language governing permissions and limitations
* * under the License.
* *
* * SPDX-License-Identifier: Apache-2.0
* *****************************************************************************
*/
package org.nd4j.autodiff.samediff;
import org.nd4j.autodiff.samediff.internal.VarId;
import java.util.*;
import java.util.stream.Collectors;
/**
* Utility class for entity classification and analysis in SameDiff execution.
* Provides methods to classify graph entities and analyze missing entities during execution failures.
*/
public class EntityClassificationUtils {
/**
* Classify a single entity by name
*/
public static EntityType classifyEntity(String entityName, SameDiff sameDiff) {
if (sameDiff.getOps().containsKey(entityName)) {
return EntityType.OPERATION;
} else if (sameDiff.getVariables().containsKey(entityName)) {
return EntityType.VARIABLE;
} else if (sameDiff.getConstantArrays().hasArray(entityName)) {
return EntityType.CONSTANT;
} else if (sameDiff.isPlaceHolder(entityName)) {
return EntityType.PLACEHOLDER;
} else {
return EntityType.UNKNOWN;
}
}
/**
* Classify a collection of entities
*/
public static EntityClassificationResult classifyEntities(Collection<String> entityNames, SameDiff sameDiff) {
EntityClassificationResult result = new EntityClassificationResult();
for (String entityName : entityNames) {
EntityType type = classifyEntity(entityName, sameDiff);
result.addEntity(entityName, type);
}
return result;
}
/**
* Get display icon for entity type
*/
public static String getEntityTypeIcon(EntityType type) {
switch (type) {
case OPERATION: return "⚙️";
case VARIABLE: return "📊";
case CONSTANT: return "📋";
case PLACEHOLDER: return "🔲";
case UNKNOWN: return "";
default: return "";
}
}
/**
* Get display name for entity type
*/
public static String getEntityTypeName(EntityType type) {
switch (type) {
case OPERATION: return "OPERATION";
case VARIABLE: return "VARIABLE";
case CONSTANT: return "CONSTANT";
case PLACEHOLDER: return "PLACEHOLDER";
case UNKNOWN: return "UNKNOWN ENTITY";
default: return "UNKNOWN";
}
}
/**
* Find variable locations across all frames
*/
public static List<VarId> findVariableLocations(String variableName,
Map<VarId, org.nd4j.autodiff.samediff.config.SDValue> nodeValueOutputs) {
List<VarId> locations = new ArrayList<>();
for (VarId varId : nodeValueOutputs.keySet()) {
if (varId.getVariable().equals(variableName)) {
locations.add(varId);
}
}
return locations;
}
/**
* Check if an entity name represents a variable that should be accessed via VarId rather than executed
*/
public static boolean isVariableEntity(String entityName, SameDiff sameDiff) {
return classifyEntity(entityName, sameDiff) == EntityType.VARIABLE;
}
/**
* Check if an entity name represents an operation that can be executed
*/
public static boolean isOperationEntity(String entityName, SameDiff sameDiff) {
return classifyEntity(entityName, sameDiff) == EntityType.OPERATION;
}
/**
* Get all entity names of a specific type from SameDiff
*/
public static Set<String> getEntitiesByType(EntityType type, SameDiff sameDiff) {
switch (type) {
case OPERATION:
return new LinkedHashSet<>(sameDiff.getOps().keySet());
case VARIABLE:
return new LinkedHashSet<>(sameDiff.getVariables().keySet());
case CONSTANT:
return new LinkedHashSet<>(sameDiff.getConstantArrays().arrayNames());
case PLACEHOLDER:
return new LinkedHashSet<>(sameDiff.variables().stream().filter(input -> input.getVariableType() == VariableType.PLACEHOLDER).map(input -> input.name()).collect(Collectors.toList()));
default:
return new LinkedHashSet<>();
}
}
}
@@ -0,0 +1,52 @@
/*
* ******************************************************************************
* *
* *
* * This program and the accompanying materials are made available under the
* * terms of the Apache License, Version 2.0 which is available at
* * https://www.apache.org/licenses/LICENSE-2.0.
* *
* * See the NOTICE file distributed with this work for additional
* * information regarding copyright ownership.
* * Unless required by applicable law or agreed to in writing, software
* * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* * License for the specific language governing permissions and limitations
* * under the License.
* *
* * SPDX-License-Identifier: Apache-2.0
* *****************************************************************************
*/
package org.nd4j.autodiff.samediff;
/**
* Entity type enumeration for SameDiff graph elements.
* Used to classify different types of entities in the computation graph.
*/
public enum EntityType {
/**
* Operation in the computation graph
*/
OPERATION,
/**
* Variable in the computation graph (including outputs of operations)
*/
VARIABLE,
/**
* Constant value in the computation graph
*/
CONSTANT,
/**
* Placeholder that requires runtime input
*/
PLACEHOLDER,
/**
* Unknown or unclassified entity
*/
UNKNOWN
}
@@ -0,0 +1,81 @@
package org.nd4j.autodiff.samediff;
import org.nd4j.autodiff.samediff.internal.ExecType;
import org.nd4j.autodiff.samediff.internal.FrameIter;
import java.util.ArrayList;
import java.util.List;
/**
* Represents a single execution step in the visualization
*/
public class ExecutionStep {
private final int stepNumber;
private final String timestamp;
private final ExecType type;
private final String name;
private final String frame;
private final int iteration;
private final FrameIter parentFrame;
private final List<String> inputs;
private final List<String> outputs;
private final String status;
public ExecutionStep(int stepNumber, String timestamp,
ExecType type, String name,
String frame, int iteration,
FrameIter parentFrame,
List<String> inputs, List<String> outputs, String status) {
this.stepNumber = stepNumber;
this.timestamp = timestamp;
this.type = type;
this.name = name;
this.frame = frame;
this.iteration = iteration;
this.parentFrame = parentFrame;
this.inputs = inputs != null ? new ArrayList<>(inputs) : new ArrayList<>();
this.outputs = outputs != null ? new ArrayList<>(outputs) : new ArrayList<>();
this.status = status;
}
// Getters
public int getStepNumber() {
return stepNumber;
}
public String getTimestamp() {
return timestamp;
}
public ExecType getType() {
return type;
}
public String getName() {
return name;
}
public String getFrame() {
return frame;
}
public int getIteration() {
return iteration;
}
public FrameIter getParentFrame() {
return parentFrame;
}
public List<String> getInputs() {
return inputs;
}
public List<String> getOutputs() {
return outputs;
}
public String getStatus() {
return status;
}
}
@@ -0,0 +1,250 @@
/*
* ******************************************************************************
* *
* *
* * This program and the accompanying materials are made available under the
* * terms of the Apache License, Version 2.0 which is available at
* * https://www.apache.org/licenses/LICENSE-2.0.
* *
* * See the NOTICE file distributed with this work for additional
* * information regarding copyright ownership.
* * Unless required by applicable law or agreed to in writing, software
* * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* * License for the specific language governing permissions and limitations
* * under the License.
* *
* * SPDX-License-Identifier: Apache-2.0
* *****************************************************************************
*/
package org.nd4j.autodiff.samediff;
import java.util.*;
/**
* Utility class for analyzing frame structures and dependencies in DAG execution plans
*/
public class FrameAnalyzer {
/**
* Analyze frame execution patterns and detect potential issues
*/
public static FrameAnalysisResult analyzeFrameExecution(DAGExecutionPlan plan) {
FrameAnalysisResult result = new FrameAnalysisResult();
// Analyze frame depth and nesting
analyzeFrameNesting(plan, result);
// Detect frame dependency cycles
detectFrameCycles(plan, result);
// Analyze frame transition patterns
analyzeTransitionPatterns(plan, result);
// Check for frame isolation issues
checkFrameIsolation(plan, result);
return result;
}
/**
* Find the critical path through frames
*/
public static List<String> findFrameCriticalPath(DAGExecutionPlan plan) {
Map<String, Integer> frameOperationCounts = new HashMap<>();
for (Map.Entry<String, List<String>> entry : plan.getFrameExecutionOrder().entrySet()) {
frameOperationCounts.put(entry.getKey(), entry.getValue().size());
}
return frameOperationCounts.entrySet().stream()
.sorted(Map.Entry.<String, Integer>comparingByValue().reversed())
.map(Map.Entry::getKey)
.collect(ArrayList::new, (list, item) -> list.add(item), ArrayList::addAll);
}
/**
* Calculate frame execution efficiency metrics
*/
public static Map<String, Double> calculateFrameEfficiency(DAGExecutionPlan plan) {
Map<String, Double> efficiency = new HashMap<>();
for (String frameName : plan.getFrameMetadata().keySet()) {
List<String> frameOps = plan.getOperationsInFrame(frameName);
Set<String> frameVars = plan.getVariablesInFrame(frameName);
if (!frameOps.isEmpty()) {
double opsToVarsRatio = frameVars.isEmpty() ? 0.0 : (double) frameOps.size() / frameVars.size();
efficiency.put(frameName, opsToVarsRatio);
}
}
return efficiency;
}
/**
* Find frames that could be parallelized
*/
public static Set<Set<String>> findParallelizableFrames(DAGExecutionPlan plan) {
Set<Set<String>> parallelGroups = new HashSet<>();
Map<String, Set<String>> frameDeps = plan.analyzeFrameDependencies();
// Find frames at the same depth with no dependencies between them
Map<Integer, Set<String>> framesByDepth = new HashMap<>();
for (Map.Entry<String, FrameMetadata> entry : plan.getFrameMetadata().entrySet()) {
framesByDepth.computeIfAbsent(entry.getValue().depth, k -> new HashSet<>()).add(entry.getKey());
}
for (Set<String> framesAtDepth : framesByDepth.values()) {
if (framesAtDepth.size() > 1) {
Set<String> parallelizable = new HashSet<>();
for (String frame : framesAtDepth) {
boolean canParallelize = true;
for (String otherFrame : framesAtDepth) {
if (!frame.equals(otherFrame)) {
Set<String> deps = frameDeps.getOrDefault(frame, Collections.emptySet());
if (deps.contains(otherFrame)) {
canParallelize = false;
break;
}
}
}
if (canParallelize) {
parallelizable.add(frame);
}
}
if (parallelizable.size() > 1) {
parallelGroups.add(parallelizable);
}
}
}
return parallelGroups;
}
private static void analyzeFrameNesting(DAGExecutionPlan plan, FrameAnalysisResult result) {
int maxDepth = 0;
Map<Integer, Integer> depthCounts = new HashMap<>();
for (FrameMetadata meta : plan.getFrameMetadata().values()) {
maxDepth = Math.max(maxDepth, meta.depth);
depthCounts.merge(meta.depth, 1, Integer::sum);
}
result.maxNestingDepth = maxDepth;
result.frameCountByDepth = depthCounts;
// Flag deeply nested frames as potential issues
if (maxDepth > 5) {
result.warnings.add("Deep frame nesting detected (depth: " + maxDepth + "). Consider flattening.");
}
}
private static void detectFrameCycles(DAGExecutionPlan plan, FrameAnalysisResult result) {
Map<String, Set<String>> frameDeps = plan.getFrameDependencies();
Set<String> visited = new HashSet<>();
Set<String> recursionStack = new HashSet<>();
for (String frame : plan.getFrameMetadata().keySet()) {
if (!visited.contains(frame)) {
if (hasCycleDFS(frame, frameDeps, visited, recursionStack, result.frameCycles)) {
result.hasCycles = true;
}
}
}
}
private static boolean hasCycleDFS(String frame, Map<String, Set<String>> deps,
Set<String> visited, Set<String> stack, List<String> cycles) {
visited.add(frame);
stack.add(frame);
Set<String> frameDeps = deps.getOrDefault(frame, Collections.emptySet());
for (String dep : frameDeps) {
if (!visited.contains(dep)) {
if (hasCycleDFS(dep, deps, visited, stack, cycles)) {
return true;
}
} else if (stack.contains(dep)) {
cycles.add("Cycle detected: " + frame + " -> " + dep);
return true;
}
}
stack.remove(frame);
return false;
}
private static void analyzeTransitionPatterns(DAGExecutionPlan plan, FrameAnalysisResult result) {
Map<FrameTransition, Integer> transitionCounts = new HashMap<>();
for (FrameMetadata meta : plan.getFrameMetadata().values()) {
for (Map.Entry<FrameTransition, Integer> entry : meta.transitionCounts.entrySet()) {
transitionCounts.merge(entry.getKey(), entry.getValue(), Integer::sum);
}
}
result.transitionPatterns = transitionCounts;
// Analyze patterns for potential optimizations
int enterExitRatio = transitionCounts.getOrDefault(FrameTransition.ENTER, 0) -
transitionCounts.getOrDefault(FrameTransition.EXIT, 0);
if (Math.abs(enterExitRatio) > 5) {
result.warnings.add("Unbalanced ENTER/EXIT transitions (difference: " + enterExitRatio + ")");
}
}
private static void checkFrameIsolation(DAGExecutionPlan plan, FrameAnalysisResult result) {
for (String frameName : plan.getFrameMetadata().keySet()) {
Set<String> inputs = plan.getFrameInputVariables().getOrDefault(frameName, Collections.emptySet());
Set<String> outputs = plan.getFrameOutputVariables().getOrDefault(frameName, Collections.emptySet());
if (inputs.isEmpty() && outputs.isEmpty()) {
List<String> frameOps = plan.getOperationsInFrame(frameName);
if (!frameOps.isEmpty()) {
result.isolatedFrames.add(frameName);
}
}
}
if (!result.isolatedFrames.isEmpty()) {
result.warnings.add("Found " + result.isolatedFrames.size() + " isolated frames with no external I/O");
}
}
/**
* Result of frame analysis
*/
public static class FrameAnalysisResult {
public int maxNestingDepth;
public Map<Integer, Integer> frameCountByDepth = new HashMap<>();
public boolean hasCycles = false;
public List<String> frameCycles = new ArrayList<>();
public Map<FrameTransition, Integer> transitionPatterns = new HashMap<>();
public List<String> isolatedFrames = new ArrayList<>();
public List<String> warnings = new ArrayList<>();
public boolean hasIssues() {
return hasCycles || !isolatedFrames.isEmpty() || !warnings.isEmpty();
}
public String getSummary() {
StringBuilder sb = new StringBuilder();
sb.append("Frame Analysis Summary:\n");
sb.append(" Max nesting depth: ").append(maxNestingDepth).append("\n");
sb.append(" Has cycles: ").append(hasCycles).append("\n");
sb.append(" Isolated frames: ").append(isolatedFrames.size()).append("\n");
sb.append(" Warnings: ").append(warnings.size()).append("\n");
if (!warnings.isEmpty()) {
sb.append("\nWarnings:\n");
for (String warning : warnings) {
sb.append(" - ").append(warning).append("\n");
}
}
return sb.toString();
}
}
}
@@ -0,0 +1,18 @@
package org.nd4j.autodiff.samediff;
import lombok.Data;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Data
public class FrameContextInfo {
private String frameName;
private int iteration;
private String parentFrame;
private int nestingDepth;
private List<String> relatedFrames = new ArrayList<>();
private Map<String, List<String>> crossFrameReferences = new HashMap<>();
}
@@ -0,0 +1,539 @@
/*
* ******************************************************************************
* *
* *
* * This program and the accompanying materials are made available under the
* * terms of the Apache License, Version 2.0 which is available at
* * https://www.apache.org/licenses/LICENSE-2.0.
* *
* * See the NOTICE file distributed with this work for additional
* * information regarding copyright ownership.
* * Unless required by applicable law or agreed to in writing, software
* * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* * License for the specific language governing permissions and limitations
* * under the License.
* *
* * SPDX-License-Identifier: Apache-2.0
* *****************************************************************************
*/
package org.nd4j.autodiff.samediff;
import java.util.*;
import java.util.stream.Collectors;
/**
* Utility class for optimizing frame execution order and identifying optimization opportunities
*/
public class FrameExecutionOptimizer {
/**
* Optimize frame execution order to minimize frame transitions
*/
public static List<String> optimizeFrameExecutionOrder(DAGExecutionPlan plan) {
List<String> originalOrder = plan.getExecutionOrder();
Map<String, String> opToFrame = new HashMap<>();
// Build operation to frame mapping
for (String opName : originalOrder) {
String frame = plan.getOperationFrame(opName);
if (frame != null) {
opToFrame.put(opName, frame);
}
}
// Group operations by frame while preserving dependencies
Map<String, List<String>> frameOps = new LinkedHashMap<>();
String currentFrame = null;
for (String opName : originalOrder) {
String opFrame = opToFrame.get(opName);
if (opFrame != null) {
if (!opFrame.equals(currentFrame)) {
currentFrame = opFrame;
}
frameOps.computeIfAbsent(currentFrame, k -> new ArrayList<>()).add(opName);
}
}
// Rebuild execution order with optimized frame grouping
List<String> optimizedOrder = new ArrayList<>();
for (List<String> ops : frameOps.values()) {
optimizedOrder.addAll(ops);
}
return optimizedOrder;
}
/**
* Identify operations that could be moved to reduce frame switches
*/
public static Map<String, String> identifyMovableOperations(DAGExecutionPlan plan) {
Map<String, String> suggestions = new HashMap<>();
List<String> execOrder = plan.getExecutionOrder();
for (int i = 1; i < execOrder.size() - 1; i++) {
String currentOp = execOrder.get(i);
String prevOp = execOrder.get(i - 1);
String nextOp = execOrder.get(i + 1);
String currentFrame = plan.getOperationFrame(currentOp);
String prevFrame = plan.getOperationFrame(prevOp);
String nextFrame = plan.getOperationFrame(nextOp);
// If current operation is in different frame but could be moved
if (currentFrame != null && prevFrame != null && nextFrame != null) {
if (!currentFrame.equals(prevFrame) && !currentFrame.equals(nextFrame) &&
prevFrame.equals(nextFrame)) {
// Check if operation has dependencies that would allow moving
if (canMoveOperation(plan, currentOp, prevFrame)) {
suggestions.put(currentOp, "Could move to frame: " + prevFrame + " to reduce frame switches");
}
}
}
}
return suggestions;
}
/**
* Find frame boundaries that could be optimized
*/
public static List<FrameBoundaryOptimization> findBoundaryOptimizations(DAGExecutionPlan plan) {
List<FrameBoundaryOptimization> optimizations = new ArrayList<>();
for (String frameName : plan.getFrameMetadata().keySet()) {
Set<String> boundaryOps = plan.getFrameBoundaryOperations().getOrDefault(frameName, Collections.emptySet());
for (String boundaryOp : boundaryOps) {
FrameBoundaryOptimization opt = analyzeBoundaryOperation(plan, frameName, boundaryOp);
if (opt != null) {
optimizations.add(opt);
}
}
}
return optimizations;
}
/**
* Calculate frame execution cost based on transitions and operations
*/
public static Map<String, Integer> calculateFrameExecutionCost(DAGExecutionPlan plan) {
Map<String, Integer> costs = new HashMap<>();
for (String frameName : plan.getFrameMetadata().keySet()) {
int cost = 0;
// Base cost from number of operations
List<String> frameOps = plan.getOperationsInFrame(frameName);
cost += frameOps.size();
// Additional cost from frame transitions
FrameMetadata meta = plan.getFrameMetadata().get(frameName);
for (Map.Entry<FrameTransition, Integer> entry : meta.transitionCounts.entrySet()) {
cost += entry.getValue() * getTransitionCost(entry.getKey());
}
// Cost from cross-frame references
Set<String> inputs = plan.getFrameInputVariables().getOrDefault(frameName, Collections.emptySet());
Set<String> outputs = plan.getFrameOutputVariables().getOrDefault(frameName, Collections.emptySet());
cost += (inputs.size() + outputs.size()) * 2;
costs.put(frameName, cost);
}
return costs;
}
/**
* Suggest frame consolidation opportunities
*/
public static List<FrameConsolidationSuggestion> suggestFrameConsolidations(DAGExecutionPlan plan) {
List<FrameConsolidationSuggestion> suggestions = new ArrayList<>();
// Find small frames that could be merged with their parent
for (FrameMetadata meta : plan.getFrameMetadata().values()) {
if (meta.totalOperations < 5 && meta.parentFrame != null) {
String parentFrame = meta.parentFrame;
FrameMetadata parentMeta = plan.getFrameMetadata().get(parentFrame);
if (parentMeta != null && !meta.hasLoops && !meta.hasConditionals) {
FrameConsolidationSuggestion suggestion = new FrameConsolidationSuggestion();
suggestion.sourceFrame = meta.frameName;
suggestion.targetFrame = parentFrame;
suggestion.reason = "Small frame (" + meta.totalOperations + " ops) without control flow";
suggestion.estimatedSavings = calculateConsolidationSavings(plan, meta.frameName, parentFrame);
suggestions.add(suggestion);
}
}
}
return suggestions;
}
/**
* Analyze parallel execution opportunities within frames
*/
public static Map<String, List<ParallelizationOpportunity>> analyzeParallelizationOpportunities(DAGExecutionPlan plan) {
Map<String, List<ParallelizationOpportunity>> opportunities = new HashMap<>();
for (String frameName : plan.getFrameMetadata().keySet()) {
List<String> frameOps = plan.getOperationsInFrame(frameName);
List<ParallelizationOpportunity> frameOpportunities = new ArrayList<>();
// Find operations that can be parallelized within the frame
Map<String, Set<String>> opDependencies = buildOperationDependencies(plan, frameOps);
List<Set<String>> parallelGroups = findParallelOperationGroups(opDependencies);
for (Set<String> group : parallelGroups) {
if (group.size() > 1) {
ParallelizationOpportunity opp = new ParallelizationOpportunity();
opp.frameName = frameName;
opp.parallelOperations = new ArrayList<>(group);
opp.estimatedSpeedup = calculateEstimatedSpeedup(group.size());
frameOpportunities.add(opp);
}
}
if (!frameOpportunities.isEmpty()) {
opportunities.put(frameName, frameOpportunities);
}
}
return opportunities;
}
/**
* Identify frames that could benefit from batching
*/
public static List<BatchingOpportunity> identifyBatchingOpportunities(DAGExecutionPlan plan) {
List<BatchingOpportunity> opportunities = new ArrayList<>();
for (String frameName : plan.getFrameMetadata().keySet()) {
FrameMetadata meta = plan.getFrameMetadata().get(frameName);
// Look for frames with similar operation patterns
if (meta.hasLoops && meta.maxIterations > 1) {
List<String> frameOps = plan.getOperationsInFrame(frameName);
// Analyze operation patterns for batching potential
Map<String, Integer> opTypeCounts = countOperationTypes(plan, frameOps);
if (hasBatchingPotential(opTypeCounts)) {
BatchingOpportunity opp = new BatchingOpportunity();
opp.frameName = frameName;
opp.iterationCount = meta.maxIterations;
opp.operationTypes = opTypeCounts;
opp.estimatedBenefit = calculateBatchingBenefit(meta.maxIterations, frameOps.size());
opportunities.add(opp);
}
}
}
return opportunities;
}
/**
* Generate optimization report for the entire execution plan
*/
public static OptimizationReport generateOptimizationReport(DAGExecutionPlan plan) {
OptimizationReport report = new OptimizationReport();
// Calculate current execution costs
report.currentExecutionCosts = calculateFrameExecutionCost(plan);
report.totalCurrentCost = report.currentExecutionCosts.values().stream().mapToInt(Integer::intValue).sum();
// Find optimization opportunities
report.movableOperations = identifyMovableOperations(plan);
report.boundaryOptimizations = findBoundaryOptimizations(plan);
report.consolidationSuggestions = suggestFrameConsolidations(plan);
report.parallelizationOpportunities = analyzeParallelizationOpportunities(plan);
report.batchingOpportunities = identifyBatchingOpportunities(plan);
// Calculate potential savings
report.estimatedSavings = calculateTotalSavings(report);
report.optimizationPriority = prioritizeOptimizations(report);
return report;
}
private static boolean canMoveOperation(DAGExecutionPlan plan, String opName, String targetFrame) {
OperationInfo opInfo = plan.getOperations().get(opName);
if (opInfo == null) return false;
// Check if all input variables are available in target frame
for (String input : opInfo.getInputs()) {
String inputFrame = plan.getVariableFrame(input);
if (inputFrame != null && !inputFrame.equals(targetFrame)) {
// Check if input is available via cross-frame reference
List<CrossFrameReference> refs = plan.getCrossFrameReferences().get(input);
if (refs == null || refs.stream().noneMatch(ref -> ref.targetFrame.equals(targetFrame))) {
return false;
}
}
}
return true;
}
private static FrameBoundaryOptimization analyzeBoundaryOperation(DAGExecutionPlan plan, String frameName, String opName) {
OperationInfo opInfo = plan.getOperations().get(opName);
if (opInfo == null || opInfo.getFrameInfo() == null) return null;
FrameTransition transition = opInfo.getFrameInfo().frameTransition;
if (transition == FrameTransition.ENTER || transition == FrameTransition.EXIT) {
FrameBoundaryOptimization opt = new FrameBoundaryOptimization();
opt.frameName = frameName;
opt.operationName = opName;
opt.transitionType = transition;
// Check if this boundary operation is necessary
if (transition == FrameTransition.ENTER) {
// Check if variables being entered are actually used in frame
Set<String> frameVars = plan.getVariablesInFrame(frameName);
boolean allInputsUsed = opInfo.getInputs().stream().allMatch(frameVars::contains);
if (!allInputsUsed) {
opt.suggestion = "Some input variables may not be used in frame";
opt.priority = OptimizationPriority.LOW;
}
}
return opt;
}
return null;
}
private static int getTransitionCost(FrameTransition transition) {
switch (transition) {
case ENTER: return 3;
case EXIT: return 3;
case SWITCH: return 5;
case MERGE: return 4;
case NEXT_ITERATION: return 6;
case LOOP_CONDITION: return 4;
default: return 1;
}
}
private static int calculateConsolidationSavings(DAGExecutionPlan plan, String sourceFrame, String targetFrame) {
int savings = 0;
// Savings from eliminated frame transitions
FrameMetadata sourceMeta = plan.getFrameMetadata().get(sourceFrame);
if (sourceMeta != null) {
for (Map.Entry<FrameTransition, Integer> entry : sourceMeta.transitionCounts.entrySet()) {
savings += entry.getValue() * getTransitionCost(entry.getKey());
}
}
// Savings from reduced cross-frame references
Set<String> inputs = plan.getFrameInputVariables().getOrDefault(sourceFrame, Collections.emptySet());
Set<String> outputs = plan.getFrameOutputVariables().getOrDefault(sourceFrame, Collections.emptySet());
savings += (inputs.size() + outputs.size()) * 2;
return savings;
}
private static Map<String, Set<String>> buildOperationDependencies(DAGExecutionPlan plan, List<String> frameOps) {
Map<String, Set<String>> dependencies = new HashMap<>();
for (String op : frameOps) {
dependencies.put(op, plan.getDependencies().getOrDefault(op, Collections.emptySet()));
}
return dependencies;
}
private static List<Set<String>> findParallelOperationGroups(Map<String, Set<String>> dependencies) {
List<Set<String>> parallelGroups = new ArrayList<>();
Set<String> processed = new HashSet<>();
for (String op : dependencies.keySet()) {
if (!processed.contains(op)) {
Set<String> group = new HashSet<>();
findParallelGroup(op, dependencies, group, processed);
if (group.size() > 1) {
parallelGroups.add(group);
}
}
}
return parallelGroups;
}
private static void findParallelGroup(String op, Map<String, Set<String>> dependencies,
Set<String> group, Set<String> processed) {
if (processed.contains(op)) return;
processed.add(op);
group.add(op);
// Find operations that don't depend on this one and this one doesn't depend on
for (String otherOp : dependencies.keySet()) {
if (!processed.contains(otherOp)) {
Set<String> opDeps = dependencies.get(op);
Set<String> otherDeps = dependencies.get(otherOp);
if (!opDeps.contains(otherOp) && !otherDeps.contains(op)) {
findParallelGroup(otherOp, dependencies, group, processed);
}
}
}
}
private static double calculateEstimatedSpeedup(int parallelOperations) {
// Simple model: speedup = min(parallelOperations, available_cores) * efficiency
int availableCores = Runtime.getRuntime().availableProcessors();
double efficiency = 0.8; // Assume 80% parallel efficiency
return Math.min(parallelOperations, availableCores) * efficiency;
}
private static Map<String, Integer> countOperationTypes(DAGExecutionPlan plan, List<String> operations) {
Map<String, Integer> counts = new HashMap<>();
for (String opName : operations) {
OperationInfo opInfo = plan.getOperations().get(opName);
if (opInfo != null) {
counts.merge(opInfo.getOpType(), 1, Integer::sum);
}
}
return counts;
}
private static boolean hasBatchingPotential(Map<String, Integer> opTypeCounts) {
// Heuristic: if there are many similar operations, batching might help
return opTypeCounts.values().stream().anyMatch(count -> count > 3);
}
private static double calculateBatchingBenefit(int iterations, int opsPerIteration) {
// Simple model: benefit increases with iterations and operations
return Math.log(iterations) * opsPerIteration * 0.1;
}
private static int calculateTotalSavings(OptimizationReport report) {
int savings = 0;
savings += report.consolidationSuggestions.stream()
.mapToInt(s -> s.estimatedSavings).sum();
savings += report.batchingOpportunities.stream()
.mapToInt(b -> (int) b.estimatedBenefit).sum();
return savings;
}
private static List<String> prioritizeOptimizations(OptimizationReport report) {
List<String> priorities = new ArrayList<>();
// High impact optimizations first
if (!report.consolidationSuggestions.isEmpty()) {
priorities.add("Frame consolidation: " + report.consolidationSuggestions.size() + " opportunities");
}
if (!report.batchingOpportunities.isEmpty()) {
priorities.add("Batching optimization: " + report.batchingOpportunities.size() + " opportunities");
}
if (!report.parallelizationOpportunities.isEmpty()) {
int totalParallelOps = report.parallelizationOpportunities.values().stream()
.mapToInt(List::size).sum();
priorities.add("Parallelization: " + totalParallelOps + " opportunities");
}
return priorities;
}
/**
* Frame boundary optimization suggestion
*/
public static class FrameBoundaryOptimization {
public String frameName;
public String operationName;
public FrameTransition transitionType;
public String suggestion;
public OptimizationPriority priority = OptimizationPriority.MEDIUM;
}
/**
* Frame consolidation suggestion
*/
public static class FrameConsolidationSuggestion {
public String sourceFrame;
public String targetFrame;
public String reason;
public int estimatedSavings;
}
/**
* Parallelization opportunity within a frame
*/
public static class ParallelizationOpportunity {
public String frameName;
public List<String> parallelOperations = new ArrayList<>();
public double estimatedSpeedup;
}
/**
* Batching opportunity for repetitive operations
*/
public static class BatchingOpportunity {
public String frameName;
public int iterationCount;
public Map<String, Integer> operationTypes = new HashMap<>();
public double estimatedBenefit;
}
/**
* Comprehensive optimization report
*/
public static class OptimizationReport {
public Map<String, Integer> currentExecutionCosts = new HashMap<>();
public int totalCurrentCost;
public Map<String, String> movableOperations = new HashMap<>();
public List<FrameBoundaryOptimization> boundaryOptimizations = new ArrayList<>();
public List<FrameConsolidationSuggestion> consolidationSuggestions = new ArrayList<>();
public Map<String, List<ParallelizationOpportunity>> parallelizationOpportunities = new HashMap<>();
public List<BatchingOpportunity> batchingOpportunities = new ArrayList<>();
public int estimatedSavings;
public List<String> optimizationPriority = new ArrayList<>();
public String generateSummary() {
StringBuilder sb = new StringBuilder();
sb.append("Optimization Report Summary\n");
sb.append("==========================\n\n");
sb.append("Current Execution Cost: ").append(totalCurrentCost).append("\n");
sb.append("Estimated Savings: ").append(estimatedSavings).append("\n");
sb.append("Potential Improvement: ").append(String.format("%.1f%%",
(double) estimatedSavings / totalCurrentCost * 100)).append("\n\n");
sb.append("Optimization Opportunities:\n");
sb.append("- Movable Operations: ").append(movableOperations.size()).append("\n");
sb.append("- Boundary Optimizations: ").append(boundaryOptimizations.size()).append("\n");
sb.append("- Consolidation Suggestions: ").append(consolidationSuggestions.size()).append("\n");
sb.append("- Parallelization Opportunities: ").append(parallelizationOpportunities.size()).append("\n");
sb.append("- Batching Opportunities: ").append(batchingOpportunities.size()).append("\n\n");
if (!optimizationPriority.isEmpty()) {
sb.append("Recommended Priority:\n");
for (int i = 0; i < optimizationPriority.size(); i++) {
sb.append((i + 1)).append(". ").append(optimizationPriority.get(i)).append("\n");
}
}
return sb.toString();
}
}
/**
* Optimization priority levels
*/
public enum OptimizationPriority {
LOW, MEDIUM, HIGH, CRITICAL
}
}
@@ -0,0 +1,15 @@
package org.nd4j.autodiff.samediff;
/**
* Information about frame context and transitions
*/
class FrameInfo {
String inputFrame;
int inputIteration;
String inputParentFrame;
String outputFrame;
int outputIteration;
String outputParentFrame;
FrameTransition frameTransition = FrameTransition.NONE;
String targetFrame; // For Enter operations
}
@@ -0,0 +1,49 @@
/*
* ******************************************************************************
* *
* *
* * This program and the accompanying materials are made available under the
* * terms of the Apache License, Version 2.0 which is available at
* * https://www.apache.org/licenses/LICENSE-2.0.
* *
* * See the NOTICE file distributed with this work for additional
* * information regarding copyright ownership.
* * Unless required by applicable law or agreed to in writing, software
* * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* * License for the specific language governing permissions and limitations
* * under the License.
* *
* * SPDX-License-Identifier: Apache-2.0
* *****************************************************************************
*/
package org.nd4j.autodiff.samediff;
import java.util.*;
/**
* Detailed frame metadata for execution analysis
*/
public class FrameMetadata {
public String frameName;
public String parentFrame;
public Set<String> childFrames = new HashSet<>();
public int depth;
public FrameType frameType;
public int totalOperations;
public int totalVariables;
public boolean hasLoops;
public boolean hasConditionals;
public int maxIterations;
public List<String> entryPoints = new ArrayList<>();
public List<String> exitPoints = new ArrayList<>();
public Map<FrameTransition, Integer> transitionCounts = new HashMap<>();
public FrameMetadata(String frameName, String parentFrame, int depth, FrameType type) {
this.frameName = frameName;
this.parentFrame = parentFrame;
this.depth = depth;
this.frameType = type;
}
}
@@ -0,0 +1,14 @@
package org.nd4j.autodiff.samediff;
/**
* Types of frame transitions
*/
enum FrameTransition {
NONE, // No frame change
ENTER, // Enter a new frame (loop/conditional)
EXIT, // Exit current frame
NEXT_ITERATION, // Move to next iteration in current frame
SWITCH, // Conditional branch
MERGE, // Merge conditional branches
LOOP_CONDITION // Loop condition check
}
@@ -0,0 +1,43 @@
/*
* ******************************************************************************
* *
* *
* * This program and the accompanying materials are made available under the
* * terms of the Apache License, Version 2.0 which is available at
* * https://www.apache.org/licenses/LICENSE-2.0.
* *
* * See the NOTICE file distributed with this work for additional
* * information regarding copyright ownership.
* * Unless required by applicable law or agreed to in writing, software
* * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* * License for the specific language governing permissions and limitations
* * under the License.
* *
* * SPDX-License-Identifier: Apache-2.0
* *****************************************************************************
*/
package org.nd4j.autodiff.samediff;
import java.util.ArrayList;
import java.util.List;
/**
* Information about frame transitions during execution
*/
public class FrameTransitionInfo {
public String fromFrame;
public String toFrame;
public FrameTransition transitionType;
public String operationName;
public List<String> affectedVariables = new ArrayList<>();
public int iterationChange;
public FrameTransitionInfo(String fromFrame, String toFrame, FrameTransition type, String operation) {
this.fromFrame = fromFrame;
this.toFrame = toFrame;
this.transitionType = type;
this.operationName = operation;
}
}
@@ -0,0 +1,32 @@
/*
* ******************************************************************************
* *
* *
* * This program and the accompanying materials are made available under the
* * terms of the Apache License, Version 2.0 which is available at
* * https://www.apache.org/licenses/LICENSE-2.0.
* *
* * See the NOTICE file distributed with this work for additional
* * information regarding copyright ownership.
* * Unless required by applicable law or agreed to in writing, software
* * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* * License for the specific language governing permissions and limitations
* * under the License.
* *
* * SPDX-License-Identifier: Apache-2.0
* *****************************************************************************
*/
package org.nd4j.autodiff.samediff;
/**
* Types of frames in execution analysis
*/
public enum FrameType {
ROOT, // Top-level frame
LOOP, // Loop frame with iterations
CONDITIONAL, // Conditional/switch frame
FUNCTION, // Function call frame
NESTED // Generic nested frame
}
@@ -0,0 +1,571 @@
/*
* ******************************************************************************
* *
* *
* * This program and the accompanying materials are made available under the
* * terms of the Apache License, Version 2.0 which is available at
* * https://www.apache.org/licenses/LICENSE-2.0.
* *
* * See the NOTICE file distributed with this work for additional
* * information regarding copyright ownership.
* * Unless required by applicable law or agreed to in writing, software
* * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* * License for the specific language governing permissions and limitations
* * under the License.
* *
* * SPDX-License-Identifier: Apache-2.0
* *****************************************************************************
*/
package org.nd4j.autodiff.samediff;
import java.util.*;
import java.util.stream.Collectors;
/**
* Utility class for visualizing frame structures and generating textual representations
*/
public class FrameVisualizer {
/**
* Generate a detailed textual representation of the frame hierarchy
*/
public static String generateFrameHierarchyDiagram(DAGExecutionPlan plan) {
StringBuilder sb = new StringBuilder();
sb.append("Frame Hierarchy Diagram\n");
sb.append("======================\n\n");
Set<String> rootFrames = plan.getFramesAtDepth(0);
if (rootFrames.isEmpty()) {
sb.append("No frames detected\n");
return sb.toString();
}
for (String rootFrame : rootFrames) {
printFrameTree(plan, rootFrame, 0, sb, new HashSet<>());
}
return sb.toString();
}
/**
* Generate execution flow diagram showing frame transitions
*/
public static String generateExecutionFlowDiagram(DAGExecutionPlan plan) {
StringBuilder sb = new StringBuilder();
sb.append("Execution Flow Diagram\n");
sb.append("=====================\n\n");
List<String> executionOrder = plan.getExecutionOrder();
String currentFrame = null;
int frameCounter = 1;
for (String opName : executionOrder) {
String opFrame = plan.getOperationFrame(opName);
if (opFrame != null && !opFrame.equals(currentFrame)) {
if (currentFrame != null) {
sb.append(" └─ End Frame: ").append(currentFrame).append("\n");
sb.append("\n");
}
sb.append("[").append(frameCounter++).append("] Start Frame: ").append(opFrame).append("\n");
currentFrame = opFrame;
}
if (opFrame != null) {
sb.append(" ├─ ").append(opName);
OperationInfo opInfo = plan.getOperations().get(opName);
if (opInfo != null && opInfo.getFrameInfo() != null &&
opInfo.getFrameInfo().frameTransition != FrameTransition.NONE) {
sb.append(" [").append(opInfo.getFrameInfo().frameTransition).append("]");
}
sb.append("\n");
}
}
if (currentFrame != null) {
sb.append(" └─ End Frame: ").append(currentFrame).append("\n");
}
return sb.toString();
}
/**
* Generate cross-frame dependency matrix
*/
public static String generateDependencyMatrix(DAGExecutionPlan plan) {
StringBuilder sb = new StringBuilder();
sb.append("Frame Dependency Matrix\n");
sb.append("======================\n\n");
List<String> frames = new ArrayList<>(plan.getFrameMetadata().keySet());
frames.sort(String::compareTo);
if (frames.isEmpty()) {
sb.append("No frames to analyze\n");
return sb.toString();
}
// Header
sb.append(" ");
for (String frame : frames) {
sb.append(String.format("%8s", frame.substring(0, Math.min(7, frame.length()))));
}
sb.append("\n");
// Matrix rows
Map<String, Set<String>> dependencies = plan.getFrameDependencies();
for (String fromFrame : frames) {
sb.append(String.format("%-8s", fromFrame.substring(0, Math.min(7, fromFrame.length()))));
Set<String> deps = dependencies.getOrDefault(fromFrame, Collections.emptySet());
for (String toFrame : frames) {
if (fromFrame.equals(toFrame)) {
sb.append(" - ");
} else if (deps.contains(toFrame)) {
sb.append(" X ");
} else {
sb.append(" . ");
}
}
sb.append("\n");
}
sb.append("\nLegend: X = depends on, . = no dependency, - = self\n");
return sb.toString();
}
/**
* Generate frame statistics summary
*/
public static String generateFrameStatistics(DAGExecutionPlan plan) {
StringBuilder sb = new StringBuilder();
sb.append("Frame Statistics Summary\n");
sb.append("=======================\n\n");
Map<String, Object> stats = plan.getDetailedFrameStats();
sb.append("Overall Statistics:\n");
sb.append(" Total Frames: ").append(stats.get("totalFrames")).append("\n");
sb.append(" Max Depth: ").append(stats.get("maxFrameDepth")).append("\n");
sb.append(" Frames with Loops: ").append(stats.get("framesWithLoops")).append("\n");
sb.append(" Frames with Conditionals: ").append(stats.get("framesWithConditionals")).append("\n");
sb.append(" Cross-frame References: ").append(stats.get("totalCrossFrameReferences")).append("\n\n");
// Frame type distribution
@SuppressWarnings("unchecked")
Map<FrameType, Long> typeDistribution =
(Map<FrameType, Long>) stats.get("frameTypeDistribution");
if (typeDistribution != null && !typeDistribution.isEmpty()) {
sb.append("Frame Type Distribution:\n");
for (Map.Entry<FrameType, Long> entry : typeDistribution.entrySet()) {
sb.append(" ").append(entry.getKey()).append(": ").append(entry.getValue()).append("\n");
}
sb.append("\n");
}
// Transition statistics
@SuppressWarnings("unchecked")
Map<FrameTransition, Integer> transitions =
(Map<FrameTransition, Integer>) stats.get("frameTransitions");
if (transitions != null && !transitions.isEmpty()) {
sb.append("Frame Transitions:\n");
for (Map.Entry<FrameTransition, Integer> entry : transitions.entrySet()) {
sb.append(" ").append(entry.getKey()).append(": ").append(entry.getValue()).append("\n");
}
sb.append("\n");
}
// Individual frame details
sb.append("Individual Frame Details:\n");
sb.append("------------------------\n");
List<FrameMetadata> sortedFrames = plan.getFrameMetadata().values().stream()
.sorted(Comparator.comparing(m -> m.frameName))
.collect(Collectors.toList());
for (FrameMetadata meta : sortedFrames) {
sb.append("Frame: ").append(meta.frameName).append("\n");
sb.append(" Type: ").append(meta.frameType).append("\n");
sb.append(" Depth: ").append(meta.depth).append("\n");
sb.append(" Parent: ").append(meta.parentFrame != null ? meta.parentFrame : "None").append("\n");
sb.append(" Operations: ").append(meta.totalOperations).append("\n");
sb.append(" Variables: ").append(meta.totalVariables).append("\n");
sb.append(" Max Iterations: ").append(meta.maxIterations).append("\n");
sb.append(" Has Loops: ").append(meta.hasLoops).append("\n");
sb.append(" Has Conditionals: ").append(meta.hasConditionals).append("\n");
if (!meta.entryPoints.isEmpty()) {
sb.append(" Entry Points: ").append(meta.entryPoints).append("\n");
}
if (!meta.exitPoints.isEmpty()) {
sb.append(" Exit Points: ").append(meta.exitPoints).append("\n");
}
if (!meta.childFrames.isEmpty()) {
sb.append(" Child Frames: ").append(meta.childFrames).append("\n");
}
sb.append("\n");
}
return sb.toString();
}
/**
* Generate a compact frame execution timeline
*/
public static String generateExecutionTimeline(DAGExecutionPlan plan) {
StringBuilder sb = new StringBuilder();
sb.append("Frame Execution Timeline\n");
sb.append("=======================\n\n");
List<String> executionOrder = plan.getExecutionOrder();
Map<String, Integer> frameStartPos = new HashMap<>();
Map<String, Integer> frameEndPos = new HashMap<>();
// Find frame start and end positions
for (int i = 0; i < executionOrder.size(); i++) {
String opName = executionOrder.get(i);
String frame = plan.getOperationFrame(opName);
if (frame != null) {
frameStartPos.putIfAbsent(frame, i);
frameEndPos.put(frame, i);
}
}
// Create timeline visualization
List<String> frames = frameStartPos.keySet().stream()
.sorted(Comparator.comparing(frameStartPos::get))
.collect(Collectors.toList());
int timelineLength = executionOrder.size();
char[] timeline = new char[timelineLength];
Arrays.fill(timeline, '.');
for (String frame : frames) {
int start = frameStartPos.get(frame);
int end = frameEndPos.get(frame);
char symbol = getFrameSymbol(frame);
for (int i = start; i <= end; i++) {
timeline[i] = symbol;
}
}
// Print timeline with labels
sb.append("Timeline (each character = 1 operation):\n");
sb.append("Position: ");
for (int i = 0; i < Math.min(timelineLength, 100); i += 10) {
sb.append(String.format("%-10d", i));
}
sb.append("\n");
sb.append("Frames: ");
for (int i = 0; i < Math.min(timelineLength, 100); i++) {
sb.append(timeline[i]);
}
sb.append("\n\n");
// Frame legend
sb.append("Frame Legend:\n");
for (String frame : frames) {
char symbol = getFrameSymbol(frame);
int start = frameStartPos.get(frame);
int end = frameEndPos.get(frame);
sb.append(" ").append(symbol).append(" = ").append(frame)
.append(" (ops ").append(start).append("-").append(end).append(")\n");
}
return sb.toString();
}
/**
* Generate cross-frame variable flow diagram
*/
public static String generateVariableFlowDiagram(DAGExecutionPlan plan) {
StringBuilder sb = new StringBuilder();
sb.append("Cross-Frame Variable Flow\n");
sb.append("========================\n\n");
Map<String, List<CrossFrameReference>> crossRefs = plan.getCrossFrameReferences();
if (crossRefs.isEmpty()) {
sb.append("No cross-frame variable references found\n");
return sb.toString();
}
// Group by source frame
Map<String, List<CrossFrameReference>> bySourceFrame = new HashMap<>();
for (List<CrossFrameReference> refs : crossRefs.values()) {
for (CrossFrameReference ref : refs) {
bySourceFrame.computeIfAbsent(ref.sourceFrame, k -> new ArrayList<>()).add(ref);
}
}
for (Map.Entry<String, List<CrossFrameReference>> entry : bySourceFrame.entrySet()) {
String sourceFrame = entry.getKey();
List<CrossFrameReference> refs = entry.getValue();
sb.append("From Frame: ").append(sourceFrame).append("\n");
Map<String, List<CrossFrameReference>> byTarget = refs.stream()
.collect(Collectors.groupingBy(ref -> ref.targetFrame));
for (Map.Entry<String, List<CrossFrameReference>> targetEntry : byTarget.entrySet()) {
String targetFrame = targetEntry.getKey();
List<CrossFrameReference> targetRefs = targetEntry.getValue();
sb.append(" └─> To Frame: ").append(targetFrame).append("\n");
for (CrossFrameReference ref : targetRefs) {
sb.append(" ├─ Variable: ").append(ref.variableName)
.append(" (").append(ref.referenceType).append(")");
if (ref.mediatingOperation != null) {
sb.append(" via ").append(ref.mediatingOperation);
}
sb.append("\n");
}
}
sb.append("\n");
}
return sb.toString();
}
/**
* Generate frame transition flow diagram
*/
public static String generateFrameTransitionDiagram(DAGExecutionPlan plan) {
StringBuilder sb = new StringBuilder();
sb.append("Frame Transition Flow\n");
sb.append("====================\n\n");
Map<String, List<FrameTransitionInfo>> transitions = plan.getFrameTransitions();
if (transitions.isEmpty()) {
sb.append("No frame transitions found\n");
return sb.toString();
}
for (Map.Entry<String, List<FrameTransitionInfo>> entry : transitions.entrySet()) {
String fromFrame = entry.getKey();
List<FrameTransitionInfo> frameTransitions = entry.getValue();
sb.append("From Frame: ").append(fromFrame).append("\n");
for (FrameTransitionInfo transition : frameTransitions) {
sb.append(" ├─ ").append(transition.transitionType)
.append("").append(transition.toFrame)
.append(" (").append(transition.operationName).append(")");
if (transition.iterationChange != 0) {
sb.append(" [iter: ").append(transition.iterationChange > 0 ? "+" : "")
.append(transition.iterationChange).append("]");
}
if (!transition.affectedVariables.isEmpty()) {
sb.append(" vars: ").append(transition.affectedVariables);
}
sb.append("\n");
}
sb.append("\n");
}
return sb.toString();
}
/**
* Generate frame execution performance analysis
*/
public static String generateFramePerformanceAnalysis(DAGExecutionPlan plan) {
StringBuilder sb = new StringBuilder();
sb.append("Frame Performance Analysis\n");
sb.append("=========================\n\n");
Map<String, Integer> frameCosts = FrameExecutionOptimizer.calculateFrameExecutionCost(plan);
int totalCost = frameCosts.values().stream().mapToInt(Integer::intValue).sum();
sb.append("Total Execution Cost: ").append(totalCost).append("\n\n");
// Sort frames by cost (descending)
List<Map.Entry<String, Integer>> sortedCosts = frameCosts.entrySet().stream()
.sorted(Map.Entry.<String, Integer>comparingByValue().reversed())
.collect(Collectors.toList());
sb.append("Frame Cost Analysis:\n");
sb.append("Frame Name Cost % of Total Operations Variables\n");
sb.append("------------------------------------------------------------------------\n");
for (Map.Entry<String, Integer> entry : sortedCosts) {
String frameName = entry.getKey();
int cost = entry.getValue();
double percentage = (double) cost / totalCost * 100;
FrameMetadata meta = plan.getFrameMetadata().get(frameName);
int ops = meta != null ? meta.totalOperations : 0;
int vars = meta != null ? meta.totalVariables : 0;
sb.append(String.format("%-28s %6d %8.1f%% %12d %10d\n",
frameName.substring(0, Math.min(28, frameName.length())),
cost, percentage, ops, vars));
}
return sb.toString();
}
/**
* Generate comprehensive frame analysis report
*/
public static String generateComprehensiveFrameReport(DAGExecutionPlan plan) {
StringBuilder sb = new StringBuilder();
sb.append("═══════════════════════════════════════════════════════════════\n");
sb.append(" COMPREHENSIVE FRAME ANALYSIS REPORT\n");
sb.append("═══════════════════════════════════════════════════════════════\n\n");
// Executive summary
sb.append("EXECUTIVE SUMMARY\n");
sb.append("================\n");
Map<String, Object> stats = plan.getDetailedFrameStats();
sb.append("• Total Frames: ").append(stats.get("totalFrames")).append("\n");
sb.append("• Maximum Nesting Depth: ").append(stats.get("maxFrameDepth")).append("\n");
sb.append("• Frames with Control Flow: ").append(
(Long) stats.get("framesWithLoops") + (Long) stats.get("framesWithConditionals")).append("\n");
sb.append("• Cross-frame Data References: ").append(stats.get("totalCrossFrameReferences")).append("\n");
// Get optimization opportunities
FrameExecutionOptimizer.OptimizationReport optReport = FrameExecutionOptimizer.generateOptimizationReport(plan);
sb.append("• Optimization Opportunities: ").append(
optReport.consolidationSuggestions.size() +
optReport.parallelizationOpportunities.size() +
optReport.batchingOpportunities.size()).append("\n");
sb.append("• Potential Performance Improvement: ").append(
String.format("%.1f%%", (double) optReport.estimatedSavings / optReport.totalCurrentCost * 100)).append("\n\n");
// Section divider
sb.append("─────────────────────────────────────────────────────────────────\n\n");
// Frame hierarchy
sb.append("1. FRAME HIERARCHY\n");
sb.append("==================\n");
sb.append(generateFrameHierarchyDiagram(plan)).append("\n");
// Frame statistics
sb.append("2. DETAILED FRAME STATISTICS\n");
sb.append("============================\n");
sb.append(generateFrameStatistics(plan)).append("\n");
// Performance analysis
sb.append("3. PERFORMANCE ANALYSIS\n");
sb.append("=======================\n");
sb.append(generateFramePerformanceAnalysis(plan)).append("\n");
// Variable flow analysis
sb.append("4. CROSS-FRAME DATA FLOW\n");
sb.append("========================\n");
sb.append(generateVariableFlowDiagram(plan)).append("\n");
// Frame transitions
sb.append("5. FRAME TRANSITIONS\n");
sb.append("===================\n");
sb.append(generateFrameTransitionDiagram(plan)).append("\n");
// Optimization recommendations
sb.append("6. OPTIMIZATION RECOMMENDATIONS\n");
sb.append("===============================\n");
sb.append(optReport.generateSummary()).append("\n");
// Timeline visualization
sb.append("7. EXECUTION TIMELINE\n");
sb.append("=====================\n");
sb.append(generateExecutionTimeline(plan)).append("\n");
sb.append("═══════════════════════════════════════════════════════════════\n");
sb.append(" END OF REPORT\n");
sb.append("═══════════════════════════════════════════════════════════════\n");
return sb.toString();
}
/**
* Generate a simple ASCII art visualization of frame structure
*/
public static String generateFrameStructureAscii(DAGExecutionPlan plan) {
StringBuilder sb = new StringBuilder();
sb.append("Frame Structure (ASCII)\n");
sb.append("======================\n\n");
Set<String> rootFrames = plan.getFramesAtDepth(0);
for (String rootFrame : rootFrames) {
generateFrameAscii(plan, rootFrame, sb, "", true);
}
return sb.toString();
}
private static void generateFrameAscii(DAGExecutionPlan plan, String frameName,
StringBuilder sb, String prefix, boolean isLast) {
FrameMetadata meta = plan.getFrameMetadata().get(frameName);
if (meta == null) return;
// Draw current frame
sb.append(prefix);
sb.append(isLast ? "└── " : "├── ");
sb.append(frameName);
sb.append(" [").append(meta.frameType).append("]");
sb.append(" (").append(meta.totalOperations).append(" ops, ");
sb.append(meta.totalVariables).append(" vars)");
if (meta.hasLoops) sb.append(" 🔄");
if (meta.hasConditionals) sb.append(" 🔀");
sb.append("\n");
// Draw children
List<String> children = new ArrayList<>(meta.childFrames);
children.sort(String::compareTo);
for (int i = 0; i < children.size(); i++) {
boolean childIsLast = (i == children.size() - 1);
String childPrefix = prefix + (isLast ? " " : "");
generateFrameAscii(plan, children.get(i), sb, childPrefix, childIsLast);
}
}
private static void printFrameTree(DAGExecutionPlan plan, String frameName, int depth,
StringBuilder sb, Set<String> visited) {
if (visited.contains(frameName)) {
sb.append(" ".repeat(depth)).append("└─ ").append(frameName).append(" [CYCLE DETECTED]\n");
return;
}
visited.add(frameName);
FrameMetadata meta = plan.getFrameMetadata().get(frameName);
if (meta != null) {
String prefix = depth == 0 ? "┌─ " : "├─ ";
sb.append(" ".repeat(depth)).append(prefix).append(frameName);
sb.append(" (").append(meta.frameType).append(", ");
sb.append("ops: ").append(meta.totalOperations).append(", ");
sb.append("vars: ").append(meta.totalVariables).append(")");
if (meta.hasLoops) sb.append(" [LOOPS]");
if (meta.hasConditionals) sb.append(" [CONDITIONALS]");
sb.append("\n");
Set<String> children = plan.getChildFrames(frameName);
for (String child : children) {
printFrameTree(plan, child, depth + 1, sb, visited);
}
}
visited.remove(frameName);
}
private static char getFrameSymbol(String frameName) {
// Simple hash-based symbol assignment
int hash = frameName.hashCode();
char[] symbols = {'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'};
return symbols[Math.abs(hash) % symbols.length];
}
}
@@ -0,0 +1,27 @@
/*
* ******************************************************************************
* *
* *
* * This program and the accompanying materials are made available under the
* * terms of the Apache License, Version 2.0 which is available at
* * https://www.apache.org/licenses/LICENSE-2.0.
* *
* * See the NOTICE file distributed with this work for additional
* * information regarding copyright ownership.
* * Unless required by applicable law or agreed to in writing, software
* * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* * License for the specific language governing permissions and limitations
* * under the License.
* *
* * SPDX-License-Identifier: Apache-2.0
* *****************************************************************************
*/
package org.nd4j.autodiff.samediff;
import org.nd4j.autodiff.samediff.internal.InferenceSession;
public interface InferenceFactory {
InferenceSession create(SameDiff sameDiff);
}
@@ -0,0 +1,21 @@
package org.nd4j.autodiff.samediff;
import lombok.Data;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Data
public class IterationSnapshot {
private int iteration;
private long timestamp;
private Map<String, Object> variableValues = new HashMap<>();
private Map<String, String> variableShapes = new HashMap<>();
private List<String> executedOperations = new ArrayList<>();
private boolean conditionEvaluated;
private Object conditionValue;
private String conditionSource;
private Map<String, Object> debugInfo = new HashMap<>();
}
@@ -0,0 +1,536 @@
package org.nd4j.autodiff.samediff;
import org.nd4j.autodiff.samediff.config.SDValue;
import org.nd4j.autodiff.samediff.config.SDValueType;
import org.nd4j.autodiff.samediff.internal.VarId;
import org.nd4j.linalg.api.ndarray.INDArray;
import java.util.*;
import java.util.stream.Collectors;
/**
* Helper methods for loop analysis in the LoopTerminationAnalyzer
*/
public class LoopAnalysisHelpers {
/**
* Analyze the cause of early termination
*
* @param loopInfo The loop information
* @param iteration The iteration at which termination occurred
* @param terminationType The type of termination that occurred
* @return Detailed analysis of the early termination cause
*/
public static String analyzeEarlyTerminationCause(LoopInfo loopInfo, int iteration,
TerminationType terminationType,
Map<String, LoopIterationTrace> iterationTraces) {
StringBuilder cause = new StringBuilder();
switch (terminationType) {
case CONDITION_FALSE:
cause.append("Loop condition became false earlier than expected");
// Analyze condition evolution
LoopIterationTrace trace = iterationTraces.get(loopInfo.getFrameName());
if (trace != null && !trace.getConditionEvaluations().isEmpty()) {
List<ConditionEvaluation> recent = trace.getConditionEvaluations().stream()
.filter(eval -> eval.getIteration() >= iteration - 3 && eval.getIteration() <= iteration)
.collect(Collectors.toList());
if (recent.size() >= 2) {
cause.append(" - Recent condition values: ");
for (ConditionEvaluation eval : recent) {
cause.append("[").append(eval.getIteration()).append(": ").append(formatValue(eval.getConditionValue())).append("] ");
}
// Analyze sudden changes
if (recent.size() >= 2) {
Object lastValue = recent.get(recent.size() - 1).getConditionValue();
Object secondLastValue = recent.get(recent.size() - 2).getConditionValue();
if (valueChangedSuddenly(secondLastValue, lastValue)) {
cause.append("\n SUDDEN CHANGE DETECTED: ");
cause.append("Previous: ").append(formatValue(secondLastValue));
cause.append(" → Current: ").append(formatValue(lastValue));
// Calculate change magnitude
Double prevNum = extractNumericValue(secondLastValue);
Double currNum = extractNumericValue(lastValue);
if (prevNum != null && currNum != null && prevNum != 0) {
double changePercent = Math.abs((currNum - prevNum) / prevNum) * 100;
cause.append(" (").append(String.format("%.1f%%", changePercent)).append(" change)");
}
}
}
// Check for convergence patterns
if (recent.size() >= 3) {
List<Double> numericValues = recent.stream()
.map(eval -> extractNumericValue(eval.getConditionValue()))
.filter(Objects::nonNull)
.collect(Collectors.toList());
if (numericValues.size() >= 3) {
double convergenceRate = calculateConvergenceRate(numericValues);
if (convergenceRate > 0.1) {
cause.append("\n RAPID CONVERGENCE: Rate = ").append(String.format("%.4f", convergenceRate));
}
}
}
}
}
// Check for variable-based causes
analyzeVariableBasedCauses(cause, loopInfo, iteration, trace);
break;
case CONDITION_TRUE_EXIT:
cause.append("Exit condition met sooner than expected");
// Analyze what caused the exit condition to be true
analyzeExitConditionCauses(cause, loopInfo, iteration, iterationTraces);
break;
case SWITCH_TERMINATION:
cause.append("Switch operation took unexpected branch leading to early exit");
// Analyze switch decision patterns
analyzeSwitchTerminationCauses(cause, loopInfo, iteration, iterationTraces);
break;
case ERROR_TERMINATION:
cause.append("Error occurred before normal termination condition");
// Analyze error patterns
analyzeErrorTerminationCauses(cause, loopInfo, iteration, iterationTraces);
break;
case TIMEOUT_TERMINATION:
cause.append("Loop exceeded maximum allowed iterations");
// Analyze timeout patterns
analyzeTimeoutCauses(cause, loopInfo, iteration);
break;
case EARLY_BREAK:
cause.append("Loop was terminated by an early break condition");
// Analyze break patterns
analyzeEarlyBreakCauses(cause, loopInfo, iteration, iterationTraces);
break;
case RESOURCE_EXHAUSTION:
cause.append("Loop terminated due to resource exhaustion");
// Analyze resource usage patterns
analyzeResourceExhaustionCauses(cause, loopInfo, iteration);
break;
case MANUAL_TERMINATION:
cause.append("Loop was manually terminated");
break;
default:
cause.append("Unexpected termination type: ").append(terminationType);
cause.append(" - This may indicate a new termination pattern");
}
// Add general early termination indicators
addGeneralEarlyTerminationIndicators(cause, loopInfo, iteration, iterationTraces);
return cause.toString();
}
/**
* Map termination type to loop status
*
* @param terminationType The type of termination
* @return The corresponding loop status
*/
public static LoopTerminationStatus mapTerminationTypeToStatus(
TerminationType terminationType) {
switch (terminationType) {
case CONDITION_FALSE:
case CONDITION_TRUE_EXIT:
return LoopTerminationStatus.TERMINATED_NORMAL;
case SWITCH_TERMINATION:
case EARLY_BREAK:
case RESOURCE_EXHAUSTION:
return LoopTerminationStatus.TERMINATED_EARLY;
case ERROR_TERMINATION:
return LoopTerminationStatus.TERMINATED_ERROR;
case TIMEOUT_TERMINATION:
return LoopTerminationStatus.TERMINATED_TIMEOUT;
case MANUAL_TERMINATION:
return LoopTerminationStatus.TERMINATED_EARLY;
default:
return LoopTerminationStatus.TERMINATED_EARLY;
}
}
/**
* Capture current loop state for debugging
*
* @param frameName The name of the loop frame
* @param iteration The current iteration
* @param nodeValueOutputs The current node value outputs
* @param sameDiff The SameDiff instance for additional context
* @return A comprehensive snapshot of the loop state
*/
public static LoopState captureLoopState(String frameName, int iteration,
Map<VarId, SDValue> nodeValueOutputs,
org.nd4j.autodiff.samediff.SameDiff sameDiff) {
LoopState state = new LoopState();
state.setIteration(iteration);
// Capture variable states in the current frame
Map<String, Object> variableStates = new HashMap<>();
Map<String, String> operationStates = new HashMap<>();
List<String> activeOperations = new ArrayList<>();
Map<String, Object> frameContext = new HashMap<>();
// Collect variables from the current frame and iteration
for (Map.Entry<VarId, SDValue> entry : nodeValueOutputs.entrySet()) {
VarId varId = entry.getKey();
// Check if this variable belongs to our frame
if (frameName.equals(varId.getFrame()) && varId.getIteration() == iteration) {
String varName = varId.getVariable();
SDValue value = entry.getValue();
if (value != null) {
Object extractedValue = extractValueFromSDValue(value);
variableStates.put(varName, extractedValue);
// Add variable metadata
frameContext.put(varName + "_type", value.getSdValueType().toString());
if (value.getSdValueType() == SDValueType.TENSOR && value.getTensorValue() != null) {
INDArray tensor = value.getTensorValue();
frameContext.put(varName + "_shape", Arrays.toString(tensor.shape()));
frameContext.put(varName + "_dataType", tensor.dataType().toString());
frameContext.put(varName + "_length", tensor.length());
}
}
}
}
// Capture operation states
for (Map.Entry<String, org.nd4j.autodiff.samediff.internal.SameDiffOp> opEntry : sameDiff.getOps().entrySet()) {
String opName = opEntry.getKey();
org.nd4j.autodiff.samediff.internal.SameDiffOp op = opEntry.getValue();
// Check if this operation is relevant to our frame
if (isOperationRelevantToFrame(op, frameName)) {
String opType = op.getOp().getClass().getSimpleName();
operationStates.put(opName, opType);
// Check if operation is currently active/executed
if (isOperationActive(opName, frameName, iteration)) {
activeOperations.add(opName);
}
}
}
// Add frame-specific context
frameContext.put("frameName", frameName);
frameContext.put("iteration", iteration);
frameContext.put("timestamp", System.currentTimeMillis());
frameContext.put("variableCount", variableStates.size());
frameContext.put("operationCount", operationStates.size());
frameContext.put("activeOperationCount", activeOperations.size());
// Add memory usage information if available
try {
Runtime runtime = Runtime.getRuntime();
long totalMemory = runtime.totalMemory();
long freeMemory = runtime.freeMemory();
long usedMemory = totalMemory - freeMemory;
frameContext.put("memoryUsed", usedMemory);
frameContext.put("memoryTotal", totalMemory);
frameContext.put("memoryFree", freeMemory);
} catch (Exception e) {
frameContext.put("memoryError", e.getMessage());
}
// Set all collected data
state.setVariableStates(variableStates);
state.setOperationStates(operationStates);
state.setActiveOperations(activeOperations);
state.setFrameContext(frameContext);
return state;
}
// Helper methods for analyzing specific termination causes
private static void analyzeVariableBasedCauses(StringBuilder cause, LoopInfo loopInfo, int iteration,
LoopIterationTrace trace) {
if (trace == null || loopInfo.getLoopVariables().isEmpty()) return;
cause.append("\n VARIABLE ANALYSIS:");
for (String varName : loopInfo.getLoopVariables()) {
List<Object> evolution = trace.getVariableEvolution().get(varName);
if (evolution != null && evolution.size() >= 2) {
Object lastValue = evolution.get(evolution.size() - 1);
Object secondLastValue = evolution.get(evolution.size() - 2);
if (isNumericallyUnstable(lastValue)) {
cause.append("\n Variable '").append(varName).append("' became unstable: ").append(formatValue(lastValue));
} else if (valueChangedSuddenly(secondLastValue, lastValue)) {
cause.append("\n Variable '").append(varName).append("' changed suddenly: ");
cause.append(formatValue(secondLastValue)).append("").append(formatValue(lastValue));
}
}
}
}
private static void analyzeExitConditionCauses(StringBuilder cause, LoopInfo loopInfo, int iteration,
Map<String, LoopIterationTrace> iterationTraces) {
cause.append("\n EXIT ANALYSIS:");
// Check what led to the exit condition being true
if (!loopInfo.getExitOperations().isEmpty()) {
cause.append("\n Exit operations: ").append(loopInfo.getExitOperations());
}
// Look for patterns in recent iterations
LoopIterationTrace trace = iterationTraces.get(loopInfo.getFrameName());
if (trace != null && !trace.getIterations().isEmpty()) {
List<IterationSnapshot> recentSnapshots = trace.getIterations().stream()
.filter(snap -> snap.getIteration() >= iteration - 2 && snap.getIteration() <= iteration)
.collect(Collectors.toList());
if (!recentSnapshots.isEmpty()) {
cause.append("\n Recent iterations showed: ");
for (IterationSnapshot snap : recentSnapshots) {
cause.append("iter").append(snap.getIteration()).append("(").append(snap.getVariableValues().size()).append(" vars) ");
}
}
}
}
private static void analyzeSwitchTerminationCauses(StringBuilder cause, LoopInfo loopInfo, int iteration,
Map<String, LoopIterationTrace> iterationTraces) {
cause.append("\n SWITCH ANALYSIS:");
if (!loopInfo.getSwitchOperations().isEmpty()) {
cause.append("\n Switch operations in loop: ").append(loopInfo.getSwitchOperations());
}
// Analyze switch decision patterns
LoopIterationTrace trace = iterationTraces.get(loopInfo.getFrameName());
if (trace != null) {
// Look for switch-related patterns in recent iterations
cause.append("\n Switch operations may have taken unexpected branches");
cause.append("\n This could indicate: predicate values changed unexpectedly,");
cause.append("\n or control flow logic differs from expected patterns");
}
}
private static void analyzeErrorTerminationCauses(StringBuilder cause, LoopInfo loopInfo, int iteration,
Map<String, LoopIterationTrace> iterationTraces) {
cause.append("\n ERROR ANALYSIS:");
cause.append("\n Error occurred at iteration ").append(iteration);
cause.append("\n This suggests: numerical instability, invalid operations,");
cause.append("\n or resource constraints during loop execution");
// Check for numerical instability patterns
LoopIterationTrace trace = iterationTraces.get(loopInfo.getFrameName());
if (trace != null) {
for (String varName : loopInfo.getLoopVariables()) {
List<Object> evolution = trace.getVariableEvolution().get(varName);
if (evolution != null && !evolution.isEmpty()) {
Object lastValue = evolution.get(evolution.size() - 1);
if (isNumericallyUnstable(lastValue)) {
cause.append("\n Variable '").append(varName).append("' shows instability: ").append(formatValue(lastValue));
}
}
}
}
}
private static void analyzeTimeoutCauses(StringBuilder cause, LoopInfo loopInfo, int iteration) {
cause.append("\n TIMEOUT ANALYSIS:");
cause.append("\n Iteration ").append(iteration).append(" exceeded maximum allowed");
cause.append("\n This suggests: infinite loop, very slow convergence,");
cause.append("\n or incorrect termination conditions");
if (loopInfo.getExpectedIterations() > 0) {
cause.append("\n Expected iterations: ").append(loopInfo.getExpectedIterations());
cause.append(" | Actual iterations: ").append(iteration);
}
}
private static void analyzeEarlyBreakCauses(StringBuilder cause, LoopInfo loopInfo, int iteration,
Map<String, LoopIterationTrace> iterationTraces) {
cause.append("\n EARLY BREAK ANALYSIS:");
cause.append("\n Loop was terminated by an early break condition");
cause.append("\n This could indicate: optimization stopping criteria met,");
cause.append("\n convergence threshold reached, or manual intervention");
}
private static void analyzeResourceExhaustionCauses(StringBuilder cause, LoopInfo loopInfo, int iteration) {
cause.append("\n RESOURCE EXHAUSTION ANALYSIS:");
cause.append("\n Loop consumed too many resources");
cause.append("\n This suggests: memory leak, excessive computation,");
cause.append("\n or inefficient algorithms within the loop");
// Add memory information if available
try {
Runtime runtime = Runtime.getRuntime();
long totalMemory = runtime.totalMemory();
long freeMemory = runtime.freeMemory();
long usedMemory = totalMemory - freeMemory;
cause.append("\n Current memory usage: ").append(usedMemory / 1024 / 1024).append(" MB");
cause.append(" of ").append(totalMemory / 1024 / 1024).append(" MB");
} catch (Exception e) {
cause.append("\n Memory information unavailable");
}
}
private static void addGeneralEarlyTerminationIndicators(StringBuilder cause, LoopInfo loopInfo, int iteration,
Map<String, LoopIterationTrace> iterationTraces) {
cause.append("\n\n GENERAL INDICATORS:");
// Check if this was much earlier than expected
if (loopInfo.getExpectedIterations() > 0 && iteration < loopInfo.getExpectedIterations() * 0.5) {
cause.append("\n SIGNIFICANTLY EARLY: Terminated at ").append(iteration);
cause.append(" iterations (expected ~").append(loopInfo.getExpectedIterations()).append(")");
}
// Check for prediction accuracy
if (!loopInfo.getTerminationPredictions().isEmpty()) {
TerminationPrediction bestPrediction = loopInfo.getTerminationPredictions()
.stream()
.max(Comparator.comparingDouble(TerminationPrediction::getConfidence))
.orElse(null);
if (bestPrediction != null) {
int predictedIteration = bestPrediction.getPredictedTerminationIteration();
int actualIteration = iteration;
int difference = Math.abs(predictedIteration - actualIteration);
cause.append("\n PREDICTION ACCURACY: Predicted ").append(predictedIteration);
cause.append(", Actual ").append(actualIteration);
cause.append(" (difference: ").append(difference).append(" iterations)");
if (difference > 5) {
cause.append("\n PREDICTION MISS: Large difference suggests unexpected behavior");
}
}
}
// Check execution time
long executionTime = System.currentTimeMillis() - loopInfo.getStartTime();
if (executionTime < 100) { // Less than 100ms
cause.append("\n RAPID TERMINATION: Loop completed in ").append(executionTime).append("ms");
}
}
// Utility methods
private static Object extractValueFromSDValue(SDValue value) {
if (value == null) return null;
switch (value.getSdValueType()) {
case TENSOR:
return value.getTensorValue();
case LIST:
return value.getListValue();
default:
return value.toString();
}
}
private static boolean isOperationRelevantToFrame(org.nd4j.autodiff.samediff.internal.SameDiffOp op, String frameName) {
// This is a simplified check - in practice, you'd need to track frame associations
// For now, we'll consider all operations potentially relevant
return true;
}
private static boolean isOperationActive(String opName, String frameName, int iteration) {
// This would need integration with the actual execution state
// For now, we'll return false as a placeholder
return false;
}
private static String formatValue(Object value) {
if (value == null) return "null";
if (value instanceof INDArray) {
INDArray arr = (INDArray) value;
if (arr.isScalar()) {
return String.format("%.6f", arr.getDouble(0));
} else if (arr.length() <= 5) {
return arr.toString();
} else {
return String.format("Array[%s] (length: %d)", Arrays.toString(arr.shape()), arr.length());
}
} else if (value instanceof Number) {
return String.format("%.6f", ((Number) value).doubleValue());
} else if (value instanceof Boolean) {
return value.toString();
}
return value.toString();
}
private static boolean valueChangedSuddenly(Object oldValue, Object newValue) {
Double oldNum = extractNumericValue(oldValue);
Double newNum = extractNumericValue(newValue);
if (oldNum != null && newNum != null && Math.abs(oldNum) > 1e-10) {
double changeRatio = Math.abs((newNum - oldNum) / oldNum);
return changeRatio > 0.5; // Changed by more than 50%
}
return false;
}
private static Double extractNumericValue(Object value) {
if (value == null) return null;
if (value instanceof Number) {
return ((Number) value).doubleValue();
} else if (value instanceof INDArray) {
INDArray arr = (INDArray) value;
if (arr.isScalar()) {
return arr.getDouble(0);
}
}
return null;
}
private static boolean isNumericallyUnstable(Object value) {
if (value instanceof Number) {
double d = ((Number) value).doubleValue();
return Double.isNaN(d) || Double.isInfinite(d) || Math.abs(d) > 1e10;
} else if (value instanceof INDArray) {
INDArray arr = (INDArray) value;
if (arr.isScalar()) {
double d = arr.getDouble(0);
return Double.isNaN(d) || Double.isInfinite(d) || Math.abs(d) > 1e10;
}
}
return false;
}
private static double calculateConvergenceRate(List<Double> values) {
if (values.size() < 2) return 0.0;
double totalChange = 0.0;
for (int i = 1; i < values.size(); i++) {
totalChange += Math.abs(values.get(i) - values.get(i-1));
}
return totalChange / (values.size() - 1);
}
}
@@ -0,0 +1,715 @@
package org.nd4j.autodiff.samediff;/*
* ******************************************************************************
* *
* *
* * This program and the accompanying materials are made available under the
* * terms of the Apache License, Version 2.0 which is available at
* * https://www.apache.org/licenses/LICENSE-2.0.
* *
* * See the NOTICE file distributed with this work for additional
* * information regarding copyright ownership.
* * Unless required by applicable law or agreed to in writing, software
* * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* * License for the specific language governing permissions and limitations
* * under the License.
* *
* * SPDX-License-Identifier: Apache-2.0
* *****************************************************************************
*/
import lombok.Data;
import lombok.NoArgsConstructor;
import org.nd4j.autodiff.samediff.internal.SameDiffOp;
import org.nd4j.autodiff.functions.DifferentialFunction;
import org.nd4j.linalg.api.ops.impl.controlflow.compat.*;
import java.util.*;
import java.util.stream.Collectors;
/**
* LoopInfo holds comprehensive information about a loop during execution.
* This class tracks all aspects of loop behavior including operations, variables,
* execution state, termination predictions, and performance metrics.
*/
@Data
@NoArgsConstructor
public class LoopInfo {
// === BASIC LOOP IDENTIFICATION ===
/**
* The name of the loop frame (e.g., "while_loop_1", "for_loop_2")
*/
private String frameName;
/**
* Unique identifier for this loop instance
*/
private String loopId;
/**
* The parent frame name, if this is a nested loop
*/
private String parentFrameName;
/**
* The depth of loop nesting (0 for outermost loop)
*/
private int nestingDepth = 0;
// === LOOP OPERATIONS ===
/**
* The main loop condition operation (typically LoopCond)
*/
private String loopCondOperation;
/**
* All exit operations that can terminate this loop
*/
private List<String> exitOperations = new ArrayList<>();
/**
* All switch operations within this loop
*/
private List<String> switchOperations = new ArrayList<>();
/**
* All NextIteration operations that advance the loop
*/
private List<String> nextIterationOperations = new ArrayList<>();
/**
* All Enter operations that feed into this loop
*/
private List<String> enterOperations = new ArrayList<>();
/**
* All Merge operations within this loop
*/
private List<String> mergeOperations = new ArrayList<>();
/**
* All other operations contained within this loop frame
*/
private Set<String> loopOperations = new HashSet<>();
// === LOOP VARIABLES ===
/**
* Variables that are modified within the loop (loop variables)
*/
private List<String> loopVariables = new ArrayList<>();
/**
* Variables that are constants within the loop
*/
private List<String> loopConstants = new ArrayList<>();
/**
* Variables that are passed into the loop from outside
*/
private List<String> inputVariables = new ArrayList<>();
/**
* Variables that are produced by the loop
*/
private List<String> outputVariables = new ArrayList<>();
/**
* Variables that serve as loop invariants
*/
private List<String> invariantVariables = new ArrayList<>();
// === EXECUTION STATE ===
/**
* Current iteration number
*/
private int currentIteration = 0;
/**
* Maximum number of iterations observed during execution
*/
private int maxIterationsObserved = 0;
/**
* Minimum number of iterations observed (for loops that reset)
*/
private int minIterationsObserved = 0;
/**
* Total number of times this loop has been executed
*/
private int executionCount = 0;
/**
* Time when loop execution started
*/
private long startTime;
/**
* Time when loop execution ended
*/
private long endTime;
/**
* Current status of the loop
*/
private LoopTerminationStatus status = LoopTerminationStatus.ACTIVE;
/**
* Reason for loop termination
*/
private String terminationReason;
// === TERMINATION PREDICTIONS ===
/**
* Predictions about when this loop will terminate
*/
private List<TerminationPrediction> terminationPredictions = new ArrayList<>();
/**
* Whether early termination has been detected
*/
private boolean earlyTerminationDetected = false;
/**
* Expected number of iterations (if known)
*/
private int expectedIterations = -1; // -1 means unknown
/**
* Confidence in the expected iteration count
*/
private double expectedIterationsConfidence = 0.0;
// === PERFORMANCE METRICS ===
/**
* Total execution time in milliseconds
*/
private long totalExecutionTime = 0;
/**
* Average time per iteration in milliseconds
*/
private double averageIterationTime = 0.0;
/**
* Peak memory usage during loop execution
*/
private long peakMemoryUsage = 0;
/**
* Average memory usage during loop execution
*/
private long averageMemoryUsage = 0;
/**
* Number of operations executed per iteration
*/
private Map<Integer, Integer> operationsPerIteration = new HashMap<>();
// === ANALYSIS DATA ===
/**
* General metadata about the loop
*/
private Map<String, Object> metadata = new HashMap<>();
/**
* Statistical information about loop behavior
*/
private Map<String, Double> statistics = new HashMap<>();
/**
* Flags indicating various loop characteristics
*/
private Map<String, Boolean> flags = new HashMap<>();
/**
* Custom properties that can be set during analysis
*/
private Map<String, Object> customProperties = new HashMap<>();
// === NESTED ENUMS ===
// === CONSTRUCTORS ===
/**
* Create a new LoopInfo with frame name
*/
public LoopInfo(String frameName) {
this.frameName = frameName;
this.loopId = generateLoopId(frameName);
this.startTime = System.currentTimeMillis();
initializeDefaults();
}
/**
* Create a new LoopInfo with frame name and parent
*/
public LoopInfo(String frameName, String parentFrameName) {
this.frameName = frameName;
this.parentFrameName = parentFrameName;
this.loopId = generateLoopId(frameName);
this.startTime = System.currentTimeMillis();
initializeDefaults();
}
/**
* Create a new LoopInfo with full details
*/
public LoopInfo(String frameName, String parentFrameName, int nestingDepth) {
this.frameName = frameName;
this.parentFrameName = parentFrameName;
this.nestingDepth = nestingDepth;
this.loopId = generateLoopId(frameName);
this.startTime = System.currentTimeMillis();
initializeDefaults();
}
// === INITIALIZATION ===
/**
* Initialize default values and flags
*/
private void initializeDefaults() {
// Initialize flags
flags.put("hasCondition", false);
flags.put("hasExit", false);
flags.put("hasSwitches", false);
flags.put("hasNextIteration", false);
flags.put("isNested", parentFrameName != null);
flags.put("isInfinite", false);
flags.put("isConverging", false);
flags.put("isOscillating", false);
flags.put("hasNumericalIssues", false);
// Initialize statistics
statistics.put("iterationsPerSecond", 0.0);
statistics.put("convergenceRate", 0.0);
statistics.put("memoryGrowthRate", 0.0);
statistics.put("operationEfficiency", 0.0);
// Initialize metadata
metadata.put("createdAt", System.currentTimeMillis());
metadata.put("version", "1.0");
}
/**
* Generate a unique loop ID
*/
private String generateLoopId(String frameName) {
return frameName + "_" + System.nanoTime();
}
// === OPERATION DISCOVERY ===
/**
* Discover and categorize all operations related to this loop
*/
public void discoverLoopOperations(SameDiff sameDiff) {
for (Map.Entry<String, SameDiffOp> entry : sameDiff.getOps().entrySet()) {
String opName = entry.getKey();
SameDiffOp op = entry.getValue();
DifferentialFunction func = op.getOp();
// Check if this operation is associated with this loop frame
if (isOperationInLoop(opName, func)) {
loopOperations.add(opName);
// Categorize the operation
if (func instanceof LoopCond) {
loopCondOperation = opName;
flags.put("hasCondition", true);
} else if (func instanceof Exit) {
exitOperations.add(opName);
flags.put("hasExit", true);
} else if (func instanceof Switch) {
switchOperations.add(opName);
flags.put("hasSwitches", true);
} else if (func instanceof NextIteration) {
nextIterationOperations.add(opName);
flags.put("hasNextIteration", true);
} else if (func instanceof Enter) {
enterOperations.add(opName);
} else if (func instanceof Merge) {
mergeOperations.add(opName);
}
}
}
// Update operation counts
metadata.put("totalOperations", loopOperations.size());
metadata.put("controlFlowOperations",
exitOperations.size() + switchOperations.size() +
nextIterationOperations.size() + enterOperations.size() +
mergeOperations.size() + (loopCondOperation != null ? 1 : 0));
}
/**
* Check if an operation belongs to this loop (simplified)
*/
private boolean isOperationInLoop(String opName, DifferentialFunction func) {
// This is a simplified check - in practice, you'd need to analyze
// the graph structure to determine frame associations
return true;
}
// === VARIABLE DISCOVERY ===
/**
* Discover and categorize variables related to this loop
*/
public void discoverLoopVariables(SameDiff sameDiff) {
// Discover loop variables from NextIteration operations
for (String nextIterOp : nextIterationOperations) {
SameDiffOp op = sameDiff.getOps().get(nextIterOp);
if (op != null) {
List<String> inputs = op.getInputsToOp();
if (inputs != null) {
for (String input : inputs) {
if (!loopVariables.contains(input)) {
loopVariables.add(input);
}
}
}
}
}
// Discover input variables from Enter operations
for (String enterOp : enterOperations) {
SameDiffOp op = sameDiff.getOps().get(enterOp);
if (op != null) {
List<String> inputs = op.getInputsToOp();
if (inputs != null) {
for (String input : inputs) {
if (!inputVariables.contains(input)) {
inputVariables.add(input);
}
}
}
}
}
// Discover output variables from Exit operations
for (String exitOp : exitOperations) {
SameDiffOp op = sameDiff.getOps().get(exitOp);
if (op != null) {
List<String> outputs = op.getOutputsOfOp();
if (outputs != null) {
for (String output : outputs) {
if (!outputVariables.contains(output)) {
outputVariables.add(output);
}
}
}
}
}
// Remove duplicates and update metadata
loopVariables = loopVariables.stream().distinct().collect(Collectors.toList());
inputVariables = inputVariables.stream().distinct().collect(Collectors.toList());
outputVariables = outputVariables.stream().distinct().collect(Collectors.toList());
metadata.put("loopVariableCount", loopVariables.size());
metadata.put("inputVariableCount", inputVariables.size());
metadata.put("outputVariableCount", outputVariables.size());
}
// === ITERATION TRACKING ===
/**
* Update iteration count and related metrics
*/
public void updateIteration(int iteration) {
this.currentIteration = iteration;
this.maxIterationsObserved = Math.max(this.maxIterationsObserved, iteration);
// Update timing statistics
long currentTime = System.currentTimeMillis();
this.totalExecutionTime = currentTime - startTime;
if (iteration > 0) {
this.averageIterationTime = (double) totalExecutionTime / iteration;
statistics.put("iterationsPerSecond", 1000.0 / averageIterationTime);
}
// Update flags
if (iteration > 1000) {
flags.put("isLongRunning", true);
}
if (iteration > expectedIterations && expectedIterations > 0) {
flags.put("exceededExpected", true);
}
}
/**
* Record operation count for an iteration
*/
public void recordOperationCount(int iteration, int operationCount) {
operationsPerIteration.put(iteration, operationCount);
// Update efficiency metric
if (!operationsPerIteration.isEmpty()) {
double avgOpsPerIter = operationsPerIteration.values().stream()
.mapToInt(Integer::intValue)
.average()
.orElse(0.0);
statistics.put("averageOperationsPerIteration", avgOpsPerIter);
}
}
// === MEMORY TRACKING ===
/**
* Update memory usage statistics
*/
public void updateMemoryUsage(long memoryUsage) {
this.peakMemoryUsage = Math.max(this.peakMemoryUsage, memoryUsage);
// Update average (simplified moving average)
if (this.averageMemoryUsage == 0) {
this.averageMemoryUsage = memoryUsage;
} else {
this.averageMemoryUsage = (long) ((this.averageMemoryUsage * 0.9) + (memoryUsage * 0.1));
}
statistics.put("memoryEfficiency", (double) averageMemoryUsage / Math.max(peakMemoryUsage, 1));
// Check for memory growth
if (memoryUsage > averageMemoryUsage * 1.5) {
flags.put("highMemoryUsage", true);
}
}
// === TERMINATION PREDICTION ===
/**
* Add a termination prediction
*/
public void addTerminationPrediction(TerminationPrediction prediction) {
terminationPredictions.add(prediction);
// Update expected iterations based on highest confidence prediction
TerminationPrediction bestPrediction = terminationPredictions.stream()
.max(Comparator.comparingDouble(TerminationPrediction::getConfidence))
.orElse(null);
if (bestPrediction != null && bestPrediction.getConfidence() > expectedIterationsConfidence) {
expectedIterations = bestPrediction.getPredictedTerminationIteration();
expectedIterationsConfidence = bestPrediction.getConfidence();
}
}
/**
* Get the most confident termination prediction
*/
public TerminationPrediction getBestTerminationPrediction() {
return terminationPredictions.stream()
.max(Comparator.comparingDouble(TerminationPrediction::getConfidence))
.orElse(null);
}
// === TERMINATION HANDLING ===
/**
* Mark the loop as terminated
*/
public void markTerminated(LoopTerminationStatus status, String reason) {
this.status = status;
this.terminationReason = reason;
this.endTime = System.currentTimeMillis();
// Update final statistics
updateFinalStatistics();
// Set termination flags
flags.put("isTerminated", true);
flags.put("terminatedNormally", status == LoopTerminationStatus.TERMINATED_NORMAL);
flags.put("terminatedEarly", status == LoopTerminationStatus.TERMINATED_EARLY);
flags.put("terminatedWithError", status == LoopTerminationStatus.TERMINATED_ERROR);
}
/**
* Update final statistics when loop terminates
*/
private void updateFinalStatistics() {
if (endTime > 0) {
totalExecutionTime = endTime - startTime;
if (maxIterationsObserved > 0) {
averageIterationTime = (double) totalExecutionTime / maxIterationsObserved;
statistics.put("finalIterationsPerSecond", 1000.0 / averageIterationTime);
}
}
// Calculate prediction accuracy
if (expectedIterations > 0) {
double accuracy = 1.0 - (double) Math.abs(maxIterationsObserved - expectedIterations) / expectedIterations;
statistics.put("predictionAccuracy", Math.max(0.0, accuracy));
}
}
// === ANALYSIS METHODS ===
/**
* Check if the loop appears to be converging
*/
public boolean isConverging() {
return flags.getOrDefault("isConverging", false);
}
/**
* Check if the loop appears to be oscillating
*/
public boolean isOscillating() {
return flags.getOrDefault("isOscillating", false);
}
/**
* Check if the loop has numerical issues
*/
public boolean hasNumericalIssues() {
return flags.getOrDefault("hasNumericalIssues", false);
}
/**
* Check if the loop is running longer than expected
*/
public boolean isRunningLongerThanExpected() {
return expectedIterations > 0 && currentIteration > expectedIterations * 1.2;
}
/**
* Get the loop efficiency (operations per second)
*/
public double getLoopEfficiency() {
return statistics.getOrDefault("iterationsPerSecond", 0.0);
}
/**
* Get the prediction accuracy
*/
public double getPredictionAccuracy() {
return statistics.getOrDefault("predictionAccuracy", 0.0);
}
// === UTILITY METHODS ===
/**
* Get a summary of the loop information
*/
public String getSummary() {
StringBuilder summary = new StringBuilder();
summary.append("Loop '").append(frameName).append("'");
summary.append(" (").append(status).append(")");
summary.append(" - Iterations: ").append(maxIterationsObserved);
if (totalExecutionTime > 0) {
summary.append(", Time: ").append(totalExecutionTime).append("ms");
}
if (expectedIterations > 0) {
summary.append(", Expected: ").append(expectedIterations);
}
if (terminationReason != null) {
summary.append(", Reason: ").append(terminationReason);
}
return summary.toString();
}
/**
* Get detailed information about the loop
*/
public String getDetailedInfo() {
StringBuilder info = new StringBuilder();
info.append("=== Loop Information ===\n");
info.append("Frame: ").append(frameName).append("\n");
info.append("Status: ").append(status).append("\n");
info.append("Iterations: ").append(maxIterationsObserved).append("\n");
info.append("Execution Time: ").append(totalExecutionTime).append("ms\n");
if (parentFrameName != null) {
info.append("Parent Frame: ").append(parentFrameName).append("\n");
info.append("Nesting Depth: ").append(nestingDepth).append("\n");
}
info.append("\nOperations:\n");
info.append(" Condition: ").append(loopCondOperation).append("\n");
info.append(" Exit: ").append(exitOperations).append("\n");
info.append(" Switch: ").append(switchOperations).append("\n");
info.append(" NextIteration: ").append(nextIterationOperations).append("\n");
info.append(" Total: ").append(loopOperations.size()).append("\n");
info.append("\nVariables:\n");
info.append(" Loop Variables: ").append(loopVariables.size()).append("\n");
info.append(" Input Variables: ").append(inputVariables.size()).append("\n");
info.append(" Output Variables: ").append(outputVariables.size()).append("\n");
if (!terminationPredictions.isEmpty()) {
info.append("\nPredictions: ").append(terminationPredictions.size()).append("\n");
TerminationPrediction best = getBestTerminationPrediction();
if (best != null) {
info.append(" Best: ").append(best.getPredictedTerminationIteration());
info.append(" (").append(String.format("%.2f", best.getConfidence())).append(")\n");
}
}
return info.toString();
}
/**
* Check if the loop has specific characteristics
*/
public boolean hasCharacteristic(String characteristic) {
return flags.getOrDefault(characteristic, false);
}
/**
* Set a characteristic flag
*/
public void setCharacteristic(String characteristic, boolean value) {
flags.put(characteristic, value);
}
/**
* Get a statistic value
*/
public double getStatistic(String name) {
return statistics.getOrDefault(name, 0.0);
}
/**
* Set a statistic value
*/
public void setStatistic(String name, double value) {
statistics.put(name, value);
}
/**
* Get metadata value
*/
public Object getMetadataValue(String key) {
return metadata.get(key);
}
/**
* Set metadata value
*/
public void setMetadataValue(String key, Object value) {
metadata.put(key, value);
}
@Override
public String toString() {
return getSummary();
}
}
@@ -0,0 +1,17 @@
package org.nd4j.autodiff.samediff;
import lombok.Data;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Data
public class LoopIterationTrace {
private String frameName;
private List<IterationSnapshot> iterations = new ArrayList<>();
private Map<String, List<Object>> variableEvolution = new HashMap<>();
private List<ConditionEvaluation> conditionEvaluations = new ArrayList<>();
private Map<String, Integer> operationExecutionCounts = new HashMap<>();
}
@@ -0,0 +1,76 @@
/*
* ******************************************************************************
* *
* *
* * This program and the accompanying materials are made available under the
* * terms of the Apache License, Version 2.0 which is available at
* * https://www.apache.org/licenses/LICENSE-2.0.
* *
* * See the NOTICE file distributed with this work for additional
* * information regarding copyright ownership.
* * Unless required by applicable law or agreed to in writing, software
* * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* * License for the specific language governing permissions and limitations
* * under the License.
* *
* * SPDX-License-Identifier: Apache-2.0
* *****************************************************************************
*/
package org.nd4j.autodiff.samediff;
/**
* Enumeration of operation roles within loop control flow
*/
public enum LoopOperationRole {
/**
* Regular operation that is not part of loop control flow
*/
REGULAR,
/**
* Loop condition operation (LoopCond) that determines when loop should terminate
*/
CONDITION,
/**
* Exit operation that exits the loop when condition is true
*/
EXIT,
/**
* Switch operation that routes values based on predicate
*/
SWITCH,
/**
* NextIteration operation that advances to the next loop iteration
*/
NEXT_ITERATION,
/**
* Enter operation that enters values into the loop frame
*/
ENTER,
/**
* Merge operation that merges values from different control flow paths
*/
MERGE,
/**
* Operation that computes loop invariant values
*/
INVARIANT,
/**
* Operation that is part of loop initialization
*/
INITIALIZATION,
/**
* Operation that is part of loop finalization/cleanup
*/
FINALIZATION
}
@@ -0,0 +1,17 @@
package org.nd4j.autodiff.samediff;
import lombok.Data;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Data
public class LoopState {
private int iteration;
private Map<String, Object> variableStates = new HashMap<>();
private Map<String, String> operationStates = new HashMap<>();
private List<String> activeOperations = new ArrayList<>();
private Map<String, Object> frameContext = new HashMap<>();
}
@@ -0,0 +1,30 @@
package org.nd4j.autodiff.samediff;
import lombok.Data;
// Data classes for the error report structure
@Data
public class LoopTerminationErrorReport {
private String frameName;
private int iteration;
private long timestamp;
private TerminationType terminationType;
private String triggerOperation;
private String terminationReason;
private boolean wasEarlyTermination;
private String earlyTerminationCause;
// Analysis sections
private VariableStateAnalysis variableStateAnalysis;
private OperationAnalysis operationAnalysis;
private FrameContextInfo frameContext;
private VariableEvolutionAnalysis variableEvolution;
private PerformanceMetrics performanceMetrics;
private RootCauseAnalysis rootCauseAnalysis;
private VisualizationData visualizationData;
// Loop-specific metrics
private long loopExecutionTime;
private int expectedIterations;
private int maxIterationsObserved;
}
@@ -0,0 +1,24 @@
package org.nd4j.autodiff.samediff;
import lombok.Data;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Data
public class LoopTerminationEvent {
private String frameName;
private int iteration;
private long timestamp;
private TerminationType terminationType;
private String triggerOperation;
private Object terminationValue;
private String terminationReason;
private Map<String, Object> contextData = new HashMap<>();
private List<String> affectedVariables = new ArrayList<>();
private LoopState loopStateAtTermination;
private boolean wasEarlyTermination;
private String earlyTerminationCause;
}
@@ -0,0 +1,515 @@
package org.nd4j.autodiff.samediff;/*
* ******************************************************************************
* *
* *
* * This program and the accompanying materials are made available under the
* * terms of the Apache License, Version 2.0 which is available at
* * https://www.apache.org/licenses/LICENSE-2.0.
* *
* * See the NOTICE file distributed with this work for additional
* * information regarding copyright ownership.
* * Unless required by applicable law or agreed to in writing, software
* * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* * License for the specific language governing permissions and limitations
* * under the License.
* *
* * SPDX-License-Identifier: Apache-2.0
* *****************************************************************************
*/
import lombok.extern.slf4j.Slf4j;
import java.util.*;
import java.util.stream.Collectors;
/**
* Methods for retrieving and analyzing loop termination events
*/
@Slf4j
public class LoopTerminationEventUtils {
/**
* Get the latest termination event for a specific frame
*
* @param frameName The name of the loop frame
* @param terminationHistory Map of frame names to their termination events
* @return The most recent termination event for the frame, or null if none exists
*/
public static LoopTerminationEvent getLatestTerminationEvent(String frameName,
Map<String, List<LoopTerminationEvent>> terminationHistory) {
List<LoopTerminationEvent> events = terminationHistory.get(frameName);
if (events != null && !events.isEmpty()) {
return events.get(events.size() - 1);
}
return null;
}
/**
* Get the latest termination event for a specific frame, with additional filtering
*
* @param frameName The name of the loop frame
* @param terminationHistory Map of frame names to their termination events
* @param terminationType Filter by specific termination type (null for any type)
* @return The most recent termination event matching criteria, or null if none exists
*/
public static LoopTerminationEvent getLatestTerminationEvent(String frameName,
Map<String, List<LoopTerminationEvent>> terminationHistory,
TerminationType terminationType) {
List<LoopTerminationEvent> events = terminationHistory.get(frameName);
if (events != null && !events.isEmpty()) {
// Filter by termination type if specified
if (terminationType != null) {
List<LoopTerminationEvent> filteredEvents = events.stream()
.filter(event -> event.getTerminationType() == terminationType)
.collect(Collectors.toList());
if (!filteredEvents.isEmpty()) {
return filteredEvents.get(filteredEvents.size() - 1);
}
} else {
return events.get(events.size() - 1);
}
}
return null;
}
/**
* Get the latest termination event across all frames
*
* @param terminationHistory Map of frame names to their termination events
* @return The most recent termination event across all frames, or null if none exists
*/
public static LoopTerminationEvent getLatestTerminationEventGlobal(Map<String, List<LoopTerminationEvent>> terminationHistory) {
LoopTerminationEvent latestEvent = null;
long latestTimestamp = 0;
for (Map.Entry<String, List<LoopTerminationEvent>> entry : terminationHistory.entrySet()) {
List<LoopTerminationEvent> events = entry.getValue();
if (events != null && !events.isEmpty()) {
LoopTerminationEvent lastEvent = events.get(events.size() - 1);
if (lastEvent.getTimestamp() > latestTimestamp) {
latestTimestamp = lastEvent.getTimestamp();
latestEvent = lastEvent;
}
}
}
return latestEvent;
}
/**
* Get the latest early termination event for a specific frame
*
* @param frameName The name of the loop frame
* @param terminationHistory Map of frame names to their termination events
* @return The most recent early termination event, or null if none exists
*/
public static LoopTerminationEvent getLatestEarlyTerminationEvent(String frameName,
Map<String, List<LoopTerminationEvent>> terminationHistory) {
List<LoopTerminationEvent> events = terminationHistory.get(frameName);
if (events != null && !events.isEmpty()) {
// Find the latest early termination event
for (int i = events.size() - 1; i >= 0; i--) {
LoopTerminationEvent event = events.get(i);
if (event.isWasEarlyTermination()) {
return event;
}
}
}
return null;
}
/**
* Get the latest termination event by timestamp for a specific frame
*
* @param frameName The name of the loop frame
* @param terminationHistory Map of frame names to their termination events
* @return The termination event with the highest timestamp, or null if none exists
*/
public static LoopTerminationEvent getLatestTerminationEventByTimestamp(String frameName,
Map<String, List<LoopTerminationEvent>> terminationHistory) {
List<LoopTerminationEvent> events = terminationHistory.get(frameName);
if (events != null && !events.isEmpty()) {
return events.stream()
.max(Comparator.comparingLong(LoopTerminationEvent::getTimestamp))
.orElse(null);
}
return null;
}
/**
* Get the latest termination event with detailed context information
*
* @param frameName The name of the loop frame
* @param terminationHistory Map of frame names to their termination events
* @return Enhanced termination event information, or null if none exists
*/
public static EnhancedTerminationEvent getLatestTerminationEventWithContext(String frameName,
Map<String, List<LoopTerminationEvent>> terminationHistory) {
List<LoopTerminationEvent> events = terminationHistory.get(frameName);
if (events != null && !events.isEmpty()) {
LoopTerminationEvent latestEvent = events.get(events.size() - 1);
// Create enhanced event with additional context
EnhancedTerminationEvent enhancedEvent = new EnhancedTerminationEvent(latestEvent);
// Add context about previous events
if (events.size() > 1) {
enhancedEvent.setPreviousEvent(events.get(events.size() - 2));
enhancedEvent.setEventSequenceNumber(events.size());
}
// Add timing analysis
if (events.size() > 1) {
long timeBetweenEvents = latestEvent.getTimestamp() - events.get(0).getTimestamp();
enhancedEvent.setTotalExecutionTime(timeBetweenEvents);
}
// Add pattern analysis
enhancedEvent.setTerminationPattern(analyzeTerminationPattern(events));
return enhancedEvent;
}
return null;
}
/**
* Get all termination events for a frame, sorted by timestamp
*
* @param frameName The name of the loop frame
* @param terminationHistory Map of frame names to their termination events
* @return List of termination events sorted by timestamp (oldest first)
*/
public static List<LoopTerminationEvent> getAllTerminationEventsSorted(String frameName,
Map<String, List<LoopTerminationEvent>> terminationHistory) {
List<LoopTerminationEvent> events = terminationHistory.get(frameName);
if (events != null && !events.isEmpty()) {
return events.stream()
.sorted(Comparator.comparingLong(LoopTerminationEvent::getTimestamp))
.collect(Collectors.toList());
}
return List.of();
}
/**
* Get termination events within a specific time range
*
* @param frameName The name of the loop frame
* @param terminationHistory Map of frame names to their termination events
* @param startTime Start of time range (inclusive)
* @param endTime End of time range (inclusive)
* @return List of termination events within the time range
*/
public static List<LoopTerminationEvent> getTerminationEventsInTimeRange(String frameName,
Map<String, List<LoopTerminationEvent>> terminationHistory,
long startTime, long endTime) {
List<LoopTerminationEvent> events = terminationHistory.get(frameName);
if (events != null && !events.isEmpty()) {
return events.stream()
.filter(event -> event.getTimestamp() >= startTime && event.getTimestamp() <= endTime)
.collect(Collectors.toList());
}
return List.of();
}
/**
* Get termination events by type
*
* @param frameName The name of the loop frame
* @param terminationHistory Map of frame names to their termination events
* @param terminationType The type of termination to filter by
* @return List of termination events of the specified type
*/
public static List<LoopTerminationEvent> getTerminationEventsByType(String frameName,
Map<String, List<LoopTerminationEvent>> terminationHistory,
TerminationType terminationType) {
List<LoopTerminationEvent> events = terminationHistory.get(frameName);
if (events != null && !events.isEmpty()) {
return events.stream()
.filter(event -> event.getTerminationType() == terminationType)
.collect(Collectors.toList());
}
return List.of();
}
/**
* Get the most recent termination event that matches specific criteria
*
* @param frameName The name of the loop frame
* @param terminationHistory Map of frame names to their termination events
* @param criteria Predicate to filter events
* @return The most recent event matching criteria, or null if none exists
*/
public static LoopTerminationEvent getLatestTerminationEventMatching(String frameName,
Map<String, List<LoopTerminationEvent>> terminationHistory,
java.util.function.Predicate<LoopTerminationEvent> criteria) {
List<LoopTerminationEvent> events = terminationHistory.get(frameName);
if (events != null && !events.isEmpty()) {
// Search from most recent to oldest
for (int i = events.size() - 1; i >= 0; i--) {
LoopTerminationEvent event = events.get(i);
if (criteria.test(event)) {
return event;
}
}
}
return null;
}
/**
* Check if a frame has any termination events
*
* @param frameName The name of the loop frame
* @param terminationHistory Map of frame names to their termination events
* @return true if the frame has termination events, false otherwise
*/
public static boolean hasTerminationEvents(String frameName,
Map<String, List<LoopTerminationEvent>> terminationHistory) {
List<LoopTerminationEvent> events = terminationHistory.get(frameName);
return events != null && !events.isEmpty();
}
/**
* Get the count of termination events for a frame
*
* @param frameName The name of the loop frame
* @param terminationHistory Map of frame names to their termination events
* @return Number of termination events for the frame
*/
public static int getTerminationEventCount(String frameName,
Map<String, List<LoopTerminationEvent>> terminationHistory) {
List<LoopTerminationEvent> events = terminationHistory.get(frameName);
return events != null ? events.size() : 0;
}
/**
* Get summary statistics for termination events
*
* @param frameName The name of the loop frame
* @param terminationHistory Map of frame names to their termination events
* @return TerminationEventSummary with statistics
*/
public static TerminationEventSummary getTerminationEventSummary(String frameName,
Map<String, List<LoopTerminationEvent>> terminationHistory) {
List<LoopTerminationEvent> events = terminationHistory.get(frameName);
if (events == null || events.isEmpty()) {
return new TerminationEventSummary(frameName, 0, 0, 0, null, null);
}
long totalEvents = events.size();
long earlyTerminations = events.stream()
.mapToLong(event -> event.isWasEarlyTermination() ? 1 : 0)
.sum();
long errorTerminations = events.stream()
.mapToLong(event -> event.getTerminationType() == TerminationType.ERROR_TERMINATION ? 1 : 0)
.sum();
LoopTerminationEvent firstEvent = events.get(0);
LoopTerminationEvent lastEvent = events.get(events.size() - 1);
return new TerminationEventSummary(frameName, totalEvents, earlyTerminations,
errorTerminations, firstEvent, lastEvent);
}
// Helper method to analyze termination patterns
private static String analyzeTerminationPattern(List<LoopTerminationEvent> events) {
if (events.size() < 2) {
return "SINGLE_EVENT";
}
// Check for consistent termination types
TerminationType lastType = events.get(events.size() - 1).getTerminationType();
boolean allSameType = events.stream()
.allMatch(event -> event.getTerminationType() == lastType);
if (allSameType) {
return "CONSISTENT_" + lastType.name();
}
// Check for alternating patterns
boolean alternating = true;
for (int i = 1; i < events.size(); i++) {
if (events.get(i).getTerminationType() == events.get(i - 1).getTerminationType()) {
alternating = false;
break;
}
}
if (alternating) {
return "ALTERNATING_PATTERN";
}
return "MIXED_PATTERN";
}
/**
* Enhanced termination event with additional context
*/
public static class EnhancedTerminationEvent {
private final LoopTerminationEvent baseEvent;
private LoopTerminationEvent previousEvent;
private int eventSequenceNumber;
private long totalExecutionTime;
private String terminationPattern;
public EnhancedTerminationEvent(LoopTerminationEvent baseEvent) {
this.baseEvent = baseEvent;
}
// Getters and setters
public LoopTerminationEvent getBaseEvent() { return baseEvent; }
public LoopTerminationEvent getPreviousEvent() { return previousEvent; }
public void setPreviousEvent(LoopTerminationEvent previousEvent) { this.previousEvent = previousEvent; }
public int getEventSequenceNumber() { return eventSequenceNumber; }
public void setEventSequenceNumber(int eventSequenceNumber) { this.eventSequenceNumber = eventSequenceNumber; }
public long getTotalExecutionTime() { return totalExecutionTime; }
public void setTotalExecutionTime(long totalExecutionTime) { this.totalExecutionTime = totalExecutionTime; }
public String getTerminationPattern() { return terminationPattern; }
public void setTerminationPattern(String terminationPattern) { this.terminationPattern = terminationPattern; }
public String getEnhancedSummary() {
StringBuilder summary = new StringBuilder();
summary.append("Event #").append(eventSequenceNumber);
summary.append(" - ").append(baseEvent.getTerminationType());
summary.append(" at iteration ").append(baseEvent.getIteration());
if (previousEvent != null) {
long timeBetween = baseEvent.getTimestamp() - previousEvent.getTimestamp();
summary.append(" (").append(timeBetween).append("ms after previous)");
}
if (terminationPattern != null) {
summary.append(" [").append(terminationPattern).append("]");
}
return summary.toString();
}
}
/**
* Summary statistics for termination events
*/
public static class TerminationEventSummary {
private final String frameName;
private final long totalEvents;
private final long earlyTerminations;
private final long errorTerminations;
private final LoopTerminationEvent firstEvent;
private final LoopTerminationEvent lastEvent;
public TerminationEventSummary(String frameName, long totalEvents, long earlyTerminations,
long errorTerminations, LoopTerminationEvent firstEvent,
LoopTerminationEvent lastEvent) {
this.frameName = frameName;
this.totalEvents = totalEvents;
this.earlyTerminations = earlyTerminations;
this.errorTerminations = errorTerminations;
this.firstEvent = firstEvent;
this.lastEvent = lastEvent;
}
// Getters
public String getFrameName() { return frameName; }
public long getTotalEvents() { return totalEvents; }
public long getEarlyTerminations() { return earlyTerminations; }
public long getErrorTerminations() { return errorTerminations; }
public LoopTerminationEvent getFirstEvent() { return firstEvent; }
public LoopTerminationEvent getLastEvent() { return lastEvent; }
public double getEarlyTerminationRate() {
return totalEvents > 0 ? (double) earlyTerminations / totalEvents : 0.0;
}
public double getErrorTerminationRate() {
return totalEvents > 0 ? (double) errorTerminations / totalEvents : 0.0;
}
public long getTotalExecutionTime() {
if (firstEvent != null && lastEvent != null) {
return lastEvent.getTimestamp() - firstEvent.getTimestamp();
}
return 0;
}
@Override
public String toString() {
return String.format("TerminationEventSummary[frame=%s, total=%d, early=%d, errors=%d, earlyRate=%.2f%%]",
frameName, totalEvents, earlyTerminations, errorTerminations,
getEarlyTerminationRate() * 100);
}
}
/**
* Get all early termination events across all frames
*/
public static List<LoopTerminationEvent> getAllEarlyTerminationEvents(
Map<String, List<LoopTerminationEvent>> terminationHistory) {
return terminationHistory.values().stream()
.flatMap(List::stream)
.filter(LoopTerminationEvent::isWasEarlyTermination)
.collect(Collectors.toList());
}
/**
* Find termination events that occurred within a time window
*/
public static List<LoopTerminationEvent> getTerminationEventsInTimeWindow(
Map<String, List<LoopTerminationEvent>> terminationHistory,
long startTime, long endTime) {
return terminationHistory.values().stream()
.flatMap(List::stream)
.filter(event -> event.getTimestamp() >= startTime && event.getTimestamp() <= endTime)
.collect(Collectors.toList());
}
/**
* Group termination events by their termination type
*/
public static Map<TerminationType, List<LoopTerminationEvent>> groupEventsByType(
Map<String, List<LoopTerminationEvent>> terminationHistory) {
return terminationHistory.values().stream()
.flatMap(List::stream)
.collect(Collectors.groupingBy(LoopTerminationEvent::getTerminationType));
}
/**
* Calculate termination statistics
*/
public static Map<String, Object> calculateTerminationStatistics(
Map<String, List<LoopTerminationEvent>> terminationHistory) {
Map<String, Object> stats = new HashMap<>();
List<LoopTerminationEvent> allEvents = terminationHistory.values().stream()
.flatMap(List::stream)
.collect(Collectors.toList());
stats.put("totalEvents", allEvents.size());
stats.put("uniqueFrames", terminationHistory.keySet().size());
// Count by type
Map<TerminationType, Long> typeCounts = allEvents.stream()
.collect(Collectors.groupingBy(LoopTerminationEvent::getTerminationType, Collectors.counting()));
stats.put("eventsByType", typeCounts);
// Early termination rate
long earlyTerminations = allEvents.stream()
.filter(LoopTerminationEvent::isWasEarlyTermination)
.count();
double earlyTerminationRate = allEvents.size() > 0 ? (double) earlyTerminations / allEvents.size() : 0.0;
stats.put("earlyTerminationRate", earlyTerminationRate);
// Average iteration at termination
double avgIteration = allEvents.stream()
.mapToInt(LoopTerminationEvent::getIteration)
.average()
.orElse(0.0);
stats.put("averageTerminationIteration", avgIteration);
return stats;
}
}
@@ -0,0 +1,16 @@
package org.nd4j.autodiff.samediff;
/**
* Possible loop termination statuses
*/
public enum LoopTerminationStatus {
ACTIVE, // Loop is currently executing
TERMINATED_NORMAL, // Loop terminated normally (condition became false)
TERMINATED_EARLY, // Loop terminated earlier than expected
TERMINATED_ERROR, // Loop terminated due to an error
TERMINATED_TIMEOUT, // Loop terminated due to timeout
TERMINATED_MANUAL, // Loop was manually terminated
TERMINATED_RESOURCE, // Loop terminated due to resource exhaustion
PAUSED, // Loop execution is paused
UNKNOWN // Status is unknown
}
@@ -0,0 +1,14 @@
package org.nd4j.autodiff.samediff;
import lombok.Data;
import java.util.HashMap;
import java.util.Map;
@Data
public class MultiLoopTerminationErrorReport {
private Map<String, LoopTerminationErrorReport> individualReports = new HashMap<>();
private CrossLoopAnalysis crossLoopAnalysis;
private long totalAnalysisTime;
private int totalLoopsAnalyzed;
}
@@ -0,0 +1,46 @@
/*
* ******************************************************************************
* *
* *
* * This program and the accompanying materials are made available under the
* * terms of the Apache License, Version 2.0 which is available at
* * https://www.apache.org/licenses/LICENSE-2.0.
* *
* * See the NOTICE file distributed with this work for additional
* * information regarding copyright ownership.
* * Unless required by applicable law or agreed to in writing, software
* * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* * License for the specific language governing permissions and limitations
* * under the License.
* *
* * SPDX-License-Identifier: Apache-2.0
* *****************************************************************************
*/
package org.nd4j.autodiff.samediff;
import lombok.Data;
import java.io.Closeable;
@Data
public class NameScope implements Closeable {
private final SameDiff sameDiff;
private final String name;
public NameScope(SameDiff sameDiff, String name){
this.sameDiff = sameDiff;
this.name = name;
}
@Override
public void close() {
sameDiff.closeNameScope(this);
}
@Override
public String toString(){
return "NameScope(" + name + ")";
}
}
@@ -0,0 +1,21 @@
package org.nd4j.autodiff.samediff;
import lombok.Data;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Data
public class OperationAnalysis {
private String loopConditionOp;
private List<String> exitOperations = new ArrayList<>();
private List<String> switchOperations = new ArrayList<>();
private List<String> nextIterationOperations = new ArrayList<>();
private List<String> enterOperations = new ArrayList<>();
private List<String> mergeOperations = new ArrayList<>();
private Map<String, Integer> operationExecutionCounts = new HashMap<>();
private Map<Integer, List<String>> recentExecutionHistory = new HashMap<>();
private OperationInfo triggerOperationInfo;
}
@@ -0,0 +1,377 @@
/*
* ******************************************************************************
* *
* *
* * This program and the accompanying materials are made available under the
* * terms of the Apache License, Version 2.0 which is available at
* * https://www.apache.org/licenses/LICENSE-2.0.
* *
* * See the NOTICE file distributed with this work for additional
* * information regarding copyright ownership.
* * Unless required by applicable law or agreed to in writing, software
* * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* * License for the specific language governing permissions and limitations
* * under the License.
* *
* * SPDX-License-Identifier: Apache-2.0
* *****************************************************************************
*/
package org.nd4j.autodiff.samediff;
import lombok.extern.slf4j.Slf4j;
import org.nd4j.autodiff.samediff.config.SDValue;
import org.nd4j.autodiff.samediff.internal.FrameIter;
import org.nd4j.autodiff.samediff.internal.SameDiffOp;
import org.nd4j.autodiff.samediff.internal.VarId;
import org.nd4j.linalg.api.ndarray.INDArray;
import java.util.*;
/**
* Utility class for analyzing operations and their context
*/
@Slf4j
public class OperationAnalysisUtils {
/**
* Determine the loop role based on operation type
*/
public static LoopOperationRole determineLoopRole(String opType) {
if (opType == null) return LoopOperationRole.REGULAR;
switch (opType.toLowerCase()) {
case "loopcond":
return LoopOperationRole.CONDITION;
case "exit":
return LoopOperationRole.EXIT;
case "switch":
return LoopOperationRole.SWITCH;
case "nextiteration":
return LoopOperationRole.NEXT_ITERATION;
case "enter":
return LoopOperationRole.ENTER;
case "merge":
return LoopOperationRole.MERGE;
default:
return LoopOperationRole.REGULAR;
}
}
/**
* Check if operation is critical for loop termination
*/
public static boolean isTerminationCriticalOperation(String opType) {
LoopOperationRole role = determineLoopRole(opType);
return role == LoopOperationRole.CONDITION ||
role == LoopOperationRole.EXIT ||
role == LoopOperationRole.SWITCH;
}
/**
* Create VarId with proper frame and iteration context
*/
public static VarId createVarId(String varName, FrameIter frameContext, FrameInfo frameInfo) {
if (frameContext != null) {
return new VarId(varName, frameContext.getFrame(), frameContext.getIteration(),null);
} else if (frameInfo != null && frameInfo.targetFrame != null) {
return new VarId(varName, frameInfo.targetFrame, 0,null);
} else {
return new VarId(varName, "OUTER_FRAME", 0,null);
}
}
/**
* Extract value for analysis, handling different SDValue types
*/
public static Object extractValueForAnalysis(SDValue value) {
return extractValueForAnalysis(value, 10);
}
/**
* Extract value for analysis with specified max elements for arrays
*/
public static Object extractValueForAnalysis(SDValue value, int maxElements) {
if (value == null) return null;
switch (value.getSdValueType()) {
case TENSOR:
INDArray tensor = value.getTensorValue();
if (tensor == null) return null;
if (tensor.isScalar()) {
return tensor.getDouble(0);
} else if (tensor.length() <= maxElements) {
return createTensorSummary(tensor, true);
} else {
return createTensorSummary(tensor, false);
}
case LIST:
return value.getListValue();
default:
return value.toString();
}
}
/**
* Create a summary representation of a tensor
*/
public static Map<String, Object> createTensorSummary(INDArray tensor, boolean includeValues) {
Map<String, Object> summary = new HashMap<>();
summary.put("shape", Arrays.toString(tensor.shape()));
summary.put("dataType", tensor.dataType().toString());
summary.put("length", tensor.length());
if (tensor.isScalar()) {
summary.put("value", tensor.getDouble(0));
summary.put("isScalar", true);
} else {
summary.put("isScalar", false);
if (includeValues && tensor.length() <= 10) {
// Show all values for small tensors
double[] values = tensor.toDoubleVector();
summary.put("values", Arrays.toString(values));
} else {
// Show statistics for large tensors
try {
summary.put("min", tensor.minNumber().doubleValue());
summary.put("max", tensor.maxNumber().doubleValue());
summary.put("mean", tensor.meanNumber().doubleValue());
} catch (Exception e) {
log.debug("Could not compute tensor statistics: {}", e.getMessage());
summary.put("statisticsError", e.getMessage());
}
}
// Check for numerical issues
NumericalHealthInfo healthInfo = analyzeTensorHealth(tensor);
summary.put("hasNaN", healthInfo.hasNaN);
summary.put("hasInf", healthInfo.hasInf);
summary.put("hasExtreme", healthInfo.hasExtreme);
summary.put("numericalHealth", healthInfo.getHealthDescription());
}
return summary;
}
/**
* Analyze tensor for numerical health issues
*/
public static NumericalHealthInfo analyzeTensorHealth(INDArray tensor) {
NumericalHealthInfo healthInfo = new NumericalHealthInfo();
if (tensor == null) {
healthInfo.isNull = true;
return healthInfo;
}
// For large tensors, sample a subset to avoid performance issues
int sampleSize = Math.min(1000, (int) tensor.length());
for (int i = 0; i < sampleSize; i++) {
double val = tensor.getDouble(i);
if (Double.isNaN(val)) {
healthInfo.hasNaN = true;
healthInfo.nanCount++;
} else if (Double.isInfinite(val)) {
healthInfo.hasInf = true;
healthInfo.infCount++;
} else if (Math.abs(val) > 1e10) {
healthInfo.hasExtreme = true;
healthInfo.extremeCount++;
}
}
// If we sampled, extrapolate counts
if (sampleSize < tensor.length()) {
double scaleFactor = (double) tensor.length() / sampleSize;
healthInfo.nanCount = (int) (healthInfo.nanCount * scaleFactor);
healthInfo.infCount = (int) (healthInfo.infCount * scaleFactor);
healthInfo.extremeCount = (int) (healthInfo.extremeCount * scaleFactor);
}
return healthInfo;
}
/**
* Estimate memory usage of a value
*/
public static long estimateValueMemoryUsage(SDValue value) {
if (value == null) return 0;
switch (value.getSdValueType()) {
case TENSOR:
INDArray tensor = value.getTensorValue();
if (tensor == null) return 0;
return tensor.length() * tensor.dataType().width();
case LIST:
// Rough estimate for list values
Object listValue = value.getListValue();
if (listValue instanceof List) {
return ((List<?>) listValue).size() * 8; // Rough estimate
}
return 100;
default:
return 50; // Placeholder for other types
}
}
/**
* Find the operation that produces a given variable
*/
public static SameDiffOp findProducerOperation(SameDiff sameDiff, String variableName) {
for (SameDiffOp op : sameDiff.getOps().values()) {
if (op.getOutputsOfOp() != null && op.getOutputsOfOp().contains(variableName)) {
return op;
}
}
return null;
}
/**
* Find operations that consume a given variable
*/
public static List<SameDiffOp> findConsumerOperations(SameDiff sameDiff, String variableName) {
List<SameDiffOp> consumers = new ArrayList<>();
for (SameDiffOp op : sameDiff.getOps().values()) {
if (op.getInputsToOp() != null && op.getInputsToOp().contains(variableName)) {
consumers.add(op);
}
}
return consumers;
}
/**
* Check if a specific value is problematic (NaN, Inf, etc.)
*/
public static boolean isProblematicValue(Object value) {
if (value == null) return false;
if (value instanceof Number) {
double d = ((Number) value).doubleValue();
return Double.isNaN(d) || Double.isInfinite(d);
}
if (value instanceof Map) {
@SuppressWarnings("unchecked")
Map<String, Object> map = (Map<String, Object>) value;
Boolean hasNaN = (Boolean) map.get("hasNaN");
Boolean hasInf = (Boolean) map.get("hasInf");
return (hasNaN != null && hasNaN) || (hasInf != null && hasInf);
}
return false;
}
/**
* Describe the specific problem with a value
*/
public static String describeProblem(Object value) {
if (value instanceof Number) {
double d = ((Number) value).doubleValue();
if (Double.isNaN(d)) return "NaN value";
if (Double.isInfinite(d)) return "Infinite value";
if (Math.abs(d) > 1e10) return "Extreme value: " + d;
}
if (value instanceof Map) {
@SuppressWarnings("unchecked")
Map<String, Object> map = (Map<String, Object>) value;
List<String> problems = new ArrayList<>();
Boolean hasNaN = (Boolean) map.get("hasNaN");
Boolean hasInf = (Boolean) map.get("hasInf");
Boolean hasExtreme = (Boolean) map.get("hasExtreme");
if (hasNaN != null && hasNaN) problems.add("Contains NaN values");
if (hasInf != null && hasInf) problems.add("Contains Infinite values");
if (hasExtreme != null && hasExtreme) problems.add("Contains extreme values");
return problems.isEmpty() ? "Unknown problem" : String.join(", ", problems);
}
return "Unknown problem";
}
/**
* Format value for display
*/
public static String formatValue(Object value) {
if (value == null) return "null";
if (value instanceof Number) {
return String.format("%.6f", ((Number) value).doubleValue());
}
if (value instanceof Map) {
@SuppressWarnings("unchecked")
Map<String, Object> map = (Map<String, Object>) value;
if (map.containsKey("isScalar") && Boolean.TRUE.equals(map.get("isScalar"))) {
return String.format("%.6f", (Double) map.get("value"));
} else {
StringBuilder sb = new StringBuilder();
sb.append("Tensor").append(map.get("shape"));
if (map.containsKey("values")) {
sb.append(" = ").append(map.get("values"));
} else {
sb.append(" [").append(map.get("dataType")).append("]");
if (map.containsKey("mean")) {
sb.append(" (mean: ").append(String.format("%.6f", (Double) map.get("mean"))).append(")");
}
}
String health = (String) map.get("numericalHealth");
if (health != null && !health.equals("HEALTHY")) {
sb.append(" [").append(health).append("]");
}
return sb.toString();
}
}
return value.toString();
}
/**
* Class to hold numerical health information
*/
public static class NumericalHealthInfo {
public boolean isNull = false;
public boolean hasNaN = false;
public boolean hasInf = false;
public boolean hasExtreme = false;
public int nanCount = 0;
public int infCount = 0;
public int extremeCount = 0;
public String getHealthDescription() {
if (isNull) return "NULL";
List<String> issues = new ArrayList<>();
if (hasNaN) issues.add(nanCount + " NaN values");
if (hasInf) issues.add(infCount + " Inf values");
if (hasExtreme) issues.add(extremeCount + " extreme values");
if (issues.isEmpty()) {
return "HEALTHY";
} else {
return "ISSUES: " + String.join(", ", issues);
}
}
public boolean isHealthy() {
return !isNull && !hasNaN && !hasInf && !hasExtreme;
}
}
}
@@ -0,0 +1,244 @@
/*
* ******************************************************************************
* *
* *
* * This program and the accompanying materials are made available under the
* * terms of the Apache License, Version 2.0 which is available at
* * https://www.apache.org/licenses/LICENSE-2.0.
* *
* * See the NOTICE file distributed with this work for additional
* * information regarding copyright ownership.
* * Unless required by applicable law or agreed to in writing, software
* * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* * License for the specific language governing permissions and limitations
* * under the License.
* *
* * SPDX-License-Identifier: Apache-2.0
* *****************************************************************************
*/
package org.nd4j.autodiff.samediff;
import lombok.Data;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Information about operation dependencies and relationships
*/
@Data
public class OperationDependencyInfo {
/**
* Map of input variable names to the operations that produce them
*/
private Map<String, String> inputDependencies = new HashMap<>();
/**
* Map of output variable names to the operations that consume them
*/
private Map<String, List<String>> outputDependencies = new HashMap<>();
/**
* Operations that must execute before this operation
*/
private List<String> predecessors = new ArrayList<>();
/**
* Operations that must execute after this operation
*/
private List<String> successors = new ArrayList<>();
/**
* Operations in the same frame/scope
*/
private List<String> siblings = new ArrayList<>();
/**
* Control dependencies (operations that must execute before this one for control flow reasons)
*/
private List<String> controlDependencies = new ArrayList<>();
/**
* Operations that are part of the same loop
*/
private List<String> loopPeers = new ArrayList<>();
/**
* Add an input dependency
*
* @param inputName name of the input variable
* @param producerOperation name of the operation that produces this input
*/
public void addInputDependency(String inputName, String producerOperation) {
inputDependencies.put(inputName, producerOperation);
if (!predecessors.contains(producerOperation)) {
predecessors.add(producerOperation);
}
}
/**
* Add an output dependency
*
* @param outputName name of the output variable
* @param consumerOperation name of the operation that consumes this output
*/
public void addOutputDependency(String outputName, String consumerOperation) {
outputDependencies.computeIfAbsent(outputName, k -> new ArrayList<>()).add(consumerOperation);
if (!successors.contains(consumerOperation)) {
successors.add(consumerOperation);
}
}
/**
* Add a control dependency
*
* @param controlOperation operation that must execute before this one
*/
public void addControlDependency(String controlOperation) {
if (!controlDependencies.contains(controlOperation)) {
controlDependencies.add(controlOperation);
}
if (!predecessors.contains(controlOperation)) {
predecessors.add(controlOperation);
}
}
/**
* Add a sibling operation (in same frame/scope)
*
* @param siblingOperation name of the sibling operation
*/
public void addSibling(String siblingOperation) {
if (!siblings.contains(siblingOperation)) {
siblings.add(siblingOperation);
}
}
/**
* Add a loop peer operation
*
* @param loopPeerOperation name of the operation in the same loop
*/
public void addLoopPeer(String loopPeerOperation) {
if (!loopPeers.contains(loopPeerOperation)) {
loopPeers.add(loopPeerOperation);
}
}
/**
* Get the producer operation for a specific input
*
* @param inputName name of the input variable
* @return name of the producer operation, or null if not found
*/
public String getInputProducer(String inputName) {
return inputDependencies.get(inputName);
}
/**
* Get all consumer operations for a specific output
*
* @param outputName name of the output variable
* @return list of consumer operation names
*/
public List<String> getOutputConsumers(String outputName) {
return outputDependencies.getOrDefault(outputName, new ArrayList<>());
}
/**
* Check if this operation has input dependencies
*
* @return true if there are input dependencies
*/
public boolean hasInputDependencies() {
return !inputDependencies.isEmpty();
}
/**
* Check if this operation has output dependencies
*
* @return true if there are output dependencies
*/
public boolean hasOutputDependencies() {
return !outputDependencies.isEmpty();
}
/**
* Check if this operation has control dependencies
*
* @return true if there are control dependencies
*/
public boolean hasControlDependencies() {
return !controlDependencies.isEmpty();
}
/**
* Get total number of dependencies
*
* @return total count of all dependencies
*/
public int getTotalDependencyCount() {
return predecessors.size() + successors.size() + controlDependencies.size();
}
/**
* Check if operation depends on another operation
*
* @param operationName name of the other operation
* @return true if this operation depends on the other operation
*/
public boolean dependsOn(String operationName) {
return predecessors.contains(operationName) ||
controlDependencies.contains(operationName) ||
inputDependencies.containsValue(operationName);
}
/**
* Check if another operation depends on this operation
*
* @param operationName name of the other operation
* @return true if the other operation depends on this operation
*/
public boolean isDependedOnBy(String operationName) {
return successors.contains(operationName) ||
outputDependencies.values().stream().anyMatch(consumers -> consumers.contains(operationName));
}
/**
* Clear all dependency information
*/
public void clearDependencies() {
inputDependencies.clear();
outputDependencies.clear();
predecessors.clear();
successors.clear();
siblings.clear();
controlDependencies.clear();
loopPeers.clear();
}
/**
* Get dependency summary as formatted string
*
* @return formatted dependency summary
*/
public String getDependencySummary() {
StringBuilder summary = new StringBuilder();
summary.append("Dependencies: ");
summary.append("Inputs: ").append(inputDependencies.size());
summary.append(", Outputs: ").append(outputDependencies.size());
summary.append(", Control: ").append(controlDependencies.size());
summary.append(", Predecessors: ").append(predecessors.size());
summary.append(", Successors: ").append(successors.size());
if (!loopPeers.isEmpty()) {
summary.append(", Loop peers: ").append(loopPeers.size());
}
return summary.toString();
}
}
@@ -0,0 +1,100 @@
/*
* ******************************************************************************
* *
* *
* * This program and the accompanying materials are made available under the
* * terms of the Apache License, Version 2.0 which is available at
* * https://www.apache.org/licenses/LICENSE-2.0.
* *
* * See the NOTICE file distributed with this work for additional
* * information regarding copyright ownership.
* * Unless required by applicable law or agreed to in writing, software
* * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* * License for the specific language governing permissions and limitations
* * under the License.
* *
* * SPDX-License-Identifier: Apache-2.0
* *****************************************************************************
*/
package org.nd4j.autodiff.samediff;
import lombok.Data;
/**
* Information about operation execution errors
*/
@Data
public class OperationErrorInfo {
/**
* Error message describing what went wrong
*/
private String errorMessage;
/**
* Type/class of the error that occurred
*/
private String errorType;
/**
* Timestamp when the error occurred
*/
private long timestamp;
/**
* Full stack trace of the error
*/
private String stackTrace;
/**
* Context information when the error occurred
*/
private String errorContext;
/**
* Whether this error is recoverable
*/
private boolean recoverable = false;
/**
* Suggested recovery actions
*/
private String recoveryActions;
/**
* Error severity level
*/
private ErrorSeverity severity = ErrorSeverity.ERROR;
/**
* Additional error metadata
*/
private java.util.Map<String, Object> errorMetadata = new java.util.HashMap<>();
/**
* Constructor for basic error info
*/
public OperationErrorInfo(String errorMessage, String errorType) {
this.errorMessage = errorMessage;
this.errorType = errorType;
this.timestamp = System.currentTimeMillis();
}
/**
* Default constructor
*/
public OperationErrorInfo() {
this.timestamp = System.currentTimeMillis();
}
/**
* Enumeration of error severity levels
*/
public enum ErrorSeverity {
WARNING,
ERROR,
CRITICAL,
FATAL
}
}
@@ -0,0 +1,172 @@
/*
* ******************************************************************************
* *
* *
* * This program and the accompanying materials are made available under the
* * terms of the Apache License, Version 2.0 which is available at
* * https://www.apache.org/licenses/LICENSE-2.0.
* *
* * See the NOTICE file distributed with this work for additional
* * information regarding copyright ownership.
* * Unless required by applicable law or agreed to in writing, software
* * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* * License for the specific language governing permissions and limitations
* * under the License.
* *
* * SPDX-License-Identifier: Apache-2.0
* *****************************************************************************
*/
package org.nd4j.autodiff.samediff;
import lombok.Data;
/**
* Record of a single operation execution
*/
@Data
public class OperationExecutionRecord {
/**
* Timestamp when execution occurred
*/
private long timestamp;
/**
* Execution time in nanoseconds
*/
private long executionTime;
/**
* Status of the execution
*/
private OperationExecutionStatus status;
/**
* Number of input variables
*/
private int inputCount;
/**
* Number of output variables
*/
private int outputCount;
/**
* Loop iteration when this execution occurred (if applicable)
*/
private int iteration = -1;
/**
* Frame name when this execution occurred (if applicable)
*/
private String frame;
/**
* Error message if execution failed
*/
private String errorMessage;
/**
* Memory usage during execution (in bytes)
*/
private long memoryUsage = 0;
/**
* Additional execution context information
*/
private java.util.Map<String, Object> executionContext = new java.util.HashMap<>();
/**
* Constructor for successful execution
*/
public OperationExecutionRecord(long timestamp, long executionTime, OperationExecutionStatus status) {
this.timestamp = timestamp;
this.executionTime = executionTime;
this.status = status;
}
/**
* Constructor for failed execution
*/
public OperationExecutionRecord(long timestamp, long executionTime, OperationExecutionStatus status, String errorMessage) {
this(timestamp, executionTime, status);
this.errorMessage = errorMessage;
}
/**
* Default constructor
*/
public OperationExecutionRecord() {
this.timestamp = System.currentTimeMillis();
}
/**
* Get execution time in milliseconds
*
* @return execution time in milliseconds
*/
public double getExecutionTimeMs() {
return executionTime / 1_000_000.0;
}
/**
* Check if this execution was successful
*
* @return true if execution was successful
*/
public boolean isSuccessful() {
return status == OperationExecutionStatus.SUCCESS || status == OperationExecutionStatus.ANALYZED;
}
/**
* Check if this execution had errors
*
* @return true if execution had errors
*/
public boolean hasError() {
return status == OperationExecutionStatus.ERROR || errorMessage != null;
}
/**
* Add context information
*
* @param key context key
* @param value context value
*/
public void addContext(String key, Object value) {
executionContext.put(key, value);
}
/**
* Get context information
*
* @param key context key
* @return context value or null if not found
*/
public Object getContext(String key) {
return executionContext.get(key);
}
/**
* Get formatted summary of this execution record
*
* @return formatted summary string
*/
public String getSummary() {
StringBuilder summary = new StringBuilder();
summary.append("Execution at ").append(new java.util.Date(timestamp));
summary.append(": ").append(status);
summary.append(" (").append(String.format("%.2f", getExecutionTimeMs())).append("ms)");
if (frame != null && iteration >= 0) {
summary.append(" [").append(frame).append(":").append(iteration).append("]");
}
if (hasError()) {
summary.append(" ERROR: ").append(errorMessage);
}
return summary.toString();
}
}
@@ -0,0 +1,71 @@
/*
* ******************************************************************************
* *
* *
* * This program and the accompanying materials are made available under the
* * terms of the Apache License, Version 2.0 which is available at
* * https://www.apache.org/licenses/LICENSE-2.0.
* *
* * See the NOTICE file distributed with this work for additional
* * information regarding copyright ownership.
* * Unless required by applicable law or agreed to in writing, software
* * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* * License for the specific language governing permissions and limitations
* * under the License.
* *
* * SPDX-License-Identifier: Apache-2.0
* *****************************************************************************
*/
package org.nd4j.autodiff.samediff;
/**
* Enumeration of possible operation execution statuses
*/
public enum OperationExecutionStatus {
/**
* Operation has not been executed yet
*/
NOT_EXECUTED,
/**
* Operation is currently being executed
*/
EXECUTING,
/**
* Operation executed successfully
*/
SUCCESS,
/**
* Operation was analyzed (values captured but not necessarily executed)
*/
ANALYZED,
/**
* Operation execution failed with an error
*/
ERROR,
/**
* Operation was skipped during execution
*/
SKIPPED,
/**
* Operation execution was cancelled
*/
CANCELLED,
/**
* Operation execution timed out
*/
TIMEOUT,
/**
* Operation status is unknown
*/
UNKNOWN
}
@@ -0,0 +1,646 @@
/*
* ******************************************************************************
* *
* *
* * This program and the accompanying materials are made available under the
* * terms of the Apache License, Version 2.0 which is available at
* * https://www.apache.org/licenses/LICENSE-2.0.
* *
* * See the NOTICE file distributed with this work for additional
* * information regarding copyright ownership.
* * Unless required by applicable law or agreed to in writing, software
* * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* * License for the specific language governing permissions and limitations
* * under the License.
* *
* * SPDX-License-Identifier: Apache-2.0
* *****************************************************************************
*/
package org.nd4j.autodiff.samediff;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.nd4j.autodiff.samediff.config.SDValue;
import org.nd4j.autodiff.samediff.internal.FrameIter;
import org.nd4j.autodiff.samediff.internal.SameDiffOp;
import org.nd4j.autodiff.samediff.internal.VarId;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
/**
* Enhanced OperationInfo class for comprehensive operation analysis and error reporting.
*
* This class provides detailed information about operations including their execution context,
* input/output analysis, error conditions, and integration with loop termination analysis.
*/
@Data
@Slf4j
public class OperationInfo {
// === BASIC OPERATION INFORMATION ===
/**
* The unique name of the operation
*/
private final String operationName;
/**
* The type of operation (e.g., "Add", "MatMul", "LoopCond")
*/
private final String operationType;
/**
* The full class name of the operation
*/
private final String className;
/**
* List of input variable names
*/
private final List<String> inputs;
/**
* List of output variable names
*/
private final List<String> outputs;
/**
* Frame information for this operation
*/
private FrameInfo frameInfo;
// === ENHANCED EXECUTION CONTEXT ===
/**
* Current input values at the time of analysis
*/
private Map<String, Object> inputValues = new HashMap<>();
/**
* Current output values at the time of analysis
*/
private Map<String, Object> outputValues = new HashMap<>();
/**
* Execution status of this operation
*/
private OperationExecutionStatus executionStatus = OperationExecutionStatus.UNKNOWN;
/**
* Error information if operation failed
*/
private OperationErrorInfo errorInfo;
/**
* Execution timing information
*/
private OperationTimingInfo timingInfo = new OperationTimingInfo();
/**
* Memory usage information for this operation
*/
private OperationMemoryInfo memoryInfo = new OperationMemoryInfo();
/**
* Operation-specific metadata
*/
private Map<String, Object> metadata = new HashMap<>();
/**
* Execution history for this operation
*/
private List<OperationExecutionRecord> executionHistory = new ArrayList<>();
/**
* Dependencies and relationships
*/
private OperationDependencyInfo dependencyInfo = new OperationDependencyInfo();
// === LOOP-SPECIFIC INFORMATION ===
/**
* Role of this operation in loop control flow
*/
private LoopOperationRole loopRole = LoopOperationRole.REGULAR;
/**
* Loop iteration context when this operation was analyzed
*/
private FrameIter loopContext;
/**
* Whether this operation is critical for loop termination
*/
private boolean isTerminationCritical = false;
/**
* Loop-specific execution patterns
*/
private Map<Integer, Object> iterationResults = new HashMap<>();
// === CONSTRUCTORS ===
/**
* Basic constructor maintaining backward compatibility
*/
public OperationInfo(String name, String opType, String className, List<String> inputs, List<String> outputs) {
this.operationName = name;
this.operationType = opType;
this.className = className;
this.inputs = inputs != null ? new ArrayList<>(inputs) : new ArrayList<>();
this.outputs = outputs != null ? new ArrayList<>(outputs) : new ArrayList<>();
// Initialize enhanced properties
initializeDefaults();
}
/**
* Enhanced constructor with frame context
*/
public OperationInfo(String name, String opType, String className, List<String> inputs, List<String> outputs,
FrameInfo frameInfo) {
this(name, opType, className, inputs, outputs);
this.frameInfo = frameInfo;
// Determine loop role based on operation type
this.loopRole = OperationAnalysisUtils.determineLoopRole(opType);
this.isTerminationCritical = OperationAnalysisUtils.isTerminationCriticalOperation(opType);
}
/**
* Full constructor with execution context
*/
public OperationInfo(String name, String opType, String className, List<String> inputs, List<String> outputs,
FrameInfo frameInfo, FrameIter loopContext) {
this(name, opType, className, inputs, outputs, frameInfo);
this.loopContext = loopContext;
}
// === INITIALIZATION ===
/**
* Initialize default values
*/
private void initializeDefaults() {
this.metadata.put("created_at", System.currentTimeMillis());
this.metadata.put("analysis_version", "2.0");
// Initialize loop role
this.loopRole = OperationAnalysisUtils.determineLoopRole(operationType);
this.isTerminationCritical = OperationAnalysisUtils.isTerminationCriticalOperation(operationType);
}
// === ANALYSIS METHODS ===
/**
* Analyze operation with current SameDiff state
*/
public void analyzeWithCurrentState(SameDiff sameDiff, Map<VarId, SDValue> nodeValueOutputs) {
analyzeWithCurrentState(sameDiff, nodeValueOutputs, null);
}
/**
* Analyze operation with current SameDiff state and specific frame context
*/
public void analyzeWithCurrentState(SameDiff sameDiff, Map<VarId, SDValue> nodeValueOutputs,
FrameIter frameContext) {
long startTime = System.nanoTime();
try {
// Update loop context if provided
if (frameContext != null) {
this.loopContext = frameContext;
}
// Analyze input values
analyzeInputValues(nodeValueOutputs, frameContext);
// Analyze output values
analyzeOutputValues(nodeValueOutputs, frameContext);
// Analyze operation dependencies
analyzeDependencies(sameDiff);
// Update memory usage
memoryInfo.updateTotalMemoryUsage();
// Record execution
recordExecution(startTime, OperationExecutionStatus.ANALYZED);
} catch (Exception e) {
recordError(e, startTime);
log.warn("Error analyzing operation '{}': {}", operationName, e.getMessage());
}
}
/**
* Analyze input values from node outputs
*/
private void analyzeInputValues(Map<VarId, SDValue> nodeValueOutputs, FrameIter frameContext) {
inputValues.clear();
for (String inputName : inputs) {
try {
VarId varId = OperationAnalysisUtils.createVarId(inputName, frameContext, frameInfo);
SDValue value = nodeValueOutputs.get(varId);
if (value != null) {
Object extractedValue = OperationAnalysisUtils.extractValueForAnalysis(value);
inputValues.put(inputName, extractedValue);
// Update memory usage
long memUsage = OperationAnalysisUtils.estimateValueMemoryUsage(value);
memoryInfo.addInputMemoryUsage(inputName, memUsage);
} else {
inputValues.put(inputName, null);
}
} catch (Exception e) {
log.debug("Could not analyze input '{}' for operation '{}': {}", inputName, operationName, e.getMessage());
inputValues.put(inputName, "ERROR: " + e.getMessage());
}
}
}
/**
* Analyze output values from node outputs
*/
private void analyzeOutputValues(Map<VarId, SDValue> nodeValueOutputs, FrameIter frameContext) {
outputValues.clear();
for (String outputName : outputs) {
try {
VarId varId = OperationAnalysisUtils.createVarId(outputName, frameContext, frameInfo);
SDValue value = nodeValueOutputs.get(varId);
if (value != null) {
Object extractedValue = OperationAnalysisUtils.extractValueForAnalysis(value);
outputValues.put(outputName, extractedValue);
// Update memory usage
long memUsage = OperationAnalysisUtils.estimateValueMemoryUsage(value);
memoryInfo.addOutputMemoryUsage(outputName, memUsage);
// Store iteration result for loop analysis
if (loopContext != null && isTerminationCritical) {
iterationResults.put(loopContext.getIteration(), extractedValue);
}
} else {
outputValues.put(outputName, null);
}
} catch (Exception e) {
log.debug("Could not analyze output '{}' for operation '{}': {}", outputName, operationName, e.getMessage());
outputValues.put(outputName, "ERROR: " + e.getMessage());
}
}
}
/**
* Analyze operation dependencies
*/
private void analyzeDependencies(SameDiff sameDiff) {
dependencyInfo.clearDependencies();
// Find operations that produce our inputs
for (String inputName : inputs) {
SameDiffOp producerOp = OperationAnalysisUtils.findProducerOperation(sameDiff, inputName);
if (producerOp != null) {
dependencyInfo.addInputDependency(inputName, producerOp.getName());
}
}
// Find operations that consume our outputs
for (String outputName : outputs) {
List<SameDiffOp> consumerOps = OperationAnalysisUtils.findConsumerOperations(sameDiff, outputName);
for (SameDiffOp consumerOp : consumerOps) {
dependencyInfo.addOutputDependency(outputName, consumerOp.getName());
}
}
}
/**
* Record successful execution
*/
private void recordExecution(long startTime, OperationExecutionStatus status) {
long endTime = System.nanoTime();
long executionTime = endTime - startTime;
this.executionStatus = status;
this.timingInfo.addExecutionTime(executionTime);
OperationExecutionRecord record = new OperationExecutionRecord();
record.setTimestamp(System.currentTimeMillis());
record.setExecutionTime(executionTime);
record.setStatus(status);
record.setInputCount(inputs.size());
record.setOutputCount(outputs.size());
if (loopContext != null) {
record.setIteration(loopContext.getIteration());
record.setFrame(loopContext.getFrame());
}
executionHistory.add(record);
// Keep history limited
if (executionHistory.size() > 100) {
executionHistory.remove(0);
}
}
/**
* Record execution error
*/
private void recordError(Exception error, long startTime) {
long endTime = System.nanoTime();
long executionTime = endTime - startTime;
this.executionStatus = OperationExecutionStatus.ERROR;
this.errorInfo = new OperationErrorInfo();
this.errorInfo.setErrorMessage(error.getMessage());
this.errorInfo.setErrorType(error.getClass().getSimpleName());
this.errorInfo.setTimestamp(System.currentTimeMillis());
this.errorInfo.setStackTrace(getStackTrace(error));
OperationExecutionRecord record = new OperationExecutionRecord();
record.setTimestamp(System.currentTimeMillis());
record.setExecutionTime(executionTime);
record.setStatus(OperationExecutionStatus.ERROR);
record.setErrorMessage(error.getMessage());
if (loopContext != null) {
record.setIteration(loopContext.getIteration());
record.setFrame(loopContext.getFrame());
}
executionHistory.add(record);
}
/**
* Get stack trace as string
*/
private String getStackTrace(Exception e) {
java.io.StringWriter sw = new java.io.StringWriter();
java.io.PrintWriter pw = new java.io.PrintWriter(sw);
e.printStackTrace(pw);
return sw.toString();
}
// === QUERY METHODS ===
/**
* Check if this operation is a loop control operation
*/
public boolean isLoopControlOperation() {
return loopRole != LoopOperationRole.REGULAR;
}
/**
* Check if this operation has execution errors
*/
public boolean hasExecutionErrors() {
return executionStatus == OperationExecutionStatus.ERROR || errorInfo != null;
}
/**
* Get the most recent execution record
*/
public OperationExecutionRecord getLatestExecutionRecord() {
if (executionHistory.isEmpty()) return null;
return executionHistory.get(executionHistory.size() - 1);
}
/**
* Get execution records for a specific iteration (loop context)
*/
public List<OperationExecutionRecord> getExecutionRecordsForIteration(int iteration) {
return executionHistory.stream()
.filter(record -> record.getIteration() == iteration)
.collect(java.util.stream.Collectors.toList());
}
/**
* Get input value for a specific input name
*/
public Object getInputValue(String inputName) {
return inputValues.get(inputName);
}
/**
* Get output value for a specific output name
*/
public Object getOutputValue(String outputName) {
return outputValues.get(outputName);
}
/**
* Check if operation has problematic values (NaN, Inf, etc.)
*/
public boolean hasProblematicValues() {
// Check input values
for (Object value : inputValues.values()) {
if (OperationAnalysisUtils.isProblematicValue(value)) return true;
}
// Check output values
for (Object value : outputValues.values()) {
if (OperationAnalysisUtils.isProblematicValue(value)) return true;
}
return false;
}
/**
* Get problematic value details
*/
public List<String> getProblematicValueDetails() {
List<String> details = new ArrayList<>();
// Check inputs
for (Map.Entry<String, Object> entry : inputValues.entrySet()) {
if (OperationAnalysisUtils.isProblematicValue(entry.getValue())) {
details.add("Input '" + entry.getKey() + "': " +
OperationAnalysisUtils.describeProblem(entry.getValue()));
}
}
// Check outputs
for (Map.Entry<String, Object> entry : outputValues.entrySet()) {
if (OperationAnalysisUtils.isProblematicValue(entry.getValue())) {
details.add("Output '" + entry.getKey() + "': " +
OperationAnalysisUtils.describeProblem(entry.getValue()));
}
}
return details;
}
/**
* Get average execution time
*/
public double getAverageExecutionTime() {
return timingInfo.getAverageExecutionTime();
}
/**
* Get total memory usage
*/
public long getTotalMemoryUsage() {
return memoryInfo.getTotalMemoryUsage();
}
/**
* Check if operation execution time is abnormally high
*/
public boolean hasAbnormalExecutionTime() {
return timingInfo.isAbnormalExecutionTime(3.0); // 3x average threshold
}
/**
* Get execution summary for loop analysis
*/
public String getExecutionSummary() {
StringBuilder summary = new StringBuilder();
summary.append("Operation: ").append(operationName);
summary.append(" (").append(operationType).append(")");
summary.append(" [").append(loopRole).append("]");
if (loopContext != null) {
summary.append(" Frame: ").append(loopContext.getFrame());
summary.append(" Iter: ").append(loopContext.getIteration());
}
summary.append(" Status: ").append(executionStatus);
if (hasExecutionErrors()) {
summary.append(" ERROR: ").append(errorInfo.getErrorMessage());
}
if (hasProblematicValues()) {
summary.append(" [PROBLEMATIC VALUES]");
}
return summary.toString();
}
/**
* Generate detailed analysis report
*/
public String generateDetailedReport() {
StringBuilder report = new StringBuilder();
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
report.append("=== OPERATION ANALYSIS REPORT ===\n");
report.append("Name: ").append(operationName).append("\n");
report.append("Type: ").append(operationType).append("\n");
report.append("Class: ").append(className).append("\n");
report.append("Loop Role: ").append(loopRole).append("\n");
report.append("Termination Critical: ").append(isTerminationCritical).append("\n");
report.append("Status: ").append(executionStatus).append("\n");
if (loopContext != null) {
report.append("Frame: ").append(loopContext.getFrame()).append("\n");
report.append("Iteration: ").append(loopContext.getIteration()).append("\n");
}
// Input/Output summary
report.append("\nInputs (").append(inputs.size()).append("): ").append(inputs).append("\n");
report.append("Outputs (").append(outputs.size()).append("): ").append(outputs).append("\n");
// Values (if available)
if (!inputValues.isEmpty()) {
report.append("\nInput Values:\n");
for (Map.Entry<String, Object> entry : inputValues.entrySet()) {
report.append(" ").append(entry.getKey()).append(" = ")
.append(OperationAnalysisUtils.formatValue(entry.getValue())).append("\n");
}
}
if (!outputValues.isEmpty()) {
report.append("\nOutput Values:\n");
for (Map.Entry<String, Object> entry : outputValues.entrySet()) {
report.append(" ").append(entry.getKey()).append(" = ")
.append(OperationAnalysisUtils.formatValue(entry.getValue())).append("\n");
}
}
// Error information
if (hasExecutionErrors()) {
report.append("\nERROR INFORMATION:\n");
report.append("Error Type: ").append(errorInfo.getErrorType()).append("\n");
report.append("Error Message: ").append(errorInfo.getErrorMessage()).append("\n");
report.append("Error Time: ").append(
LocalDateTime.ofInstant(java.time.Instant.ofEpochMilli(errorInfo.getTimestamp()),
java.time.ZoneId.systemDefault()).format(formatter)).append("\n");
}
// Performance metrics
report.append("\nPERFORMANCE METRICS:\n");
report.append("Executions: ").append(executionHistory.size()).append("\n");
report.append(timingInfo.getTimingStatistics()).append("\n");
report.append(memoryInfo.getMemoryUsageSummary()).append("\n");
// Problematic values
List<String> problems = getProblematicValueDetails();
if (!problems.isEmpty()) {
report.append("\nPROBLEMATIC VALUES:\n");
for (String problem : problems) {
report.append(" ⚠️ ").append(problem).append("\n");
}
}
// Dependencies
if (dependencyInfo.hasInputDependencies() || dependencyInfo.hasOutputDependencies()) {
report.append("\nDEPENDENCIES:\n");
report.append(dependencyInfo.getDependencySummary()).append("\n");
}
return report.toString();
}
// === DEPRECATED COMPATIBILITY ===
/**
* @deprecated Use operationName instead
*/
@Deprecated
public String getName() {
return operationName;
}
/**
* @deprecated Use operationType instead
*/
@Deprecated
public String getOpType() {
return operationType;
}
/**
* @deprecated Access frameInfo directly
*/
@Deprecated
public FrameInfo getFrameInfo() {
return frameInfo;
}
/**
* @deprecated Use inputs field directly
*/
@Deprecated
public List<String> getInputs() {
return inputs;
}
/**
* @deprecated Use outputs field directly
*/
@Deprecated
public List<String> getOutputs() {
return outputs;
}
}
@@ -0,0 +1,223 @@
/*
* ******************************************************************************
* *
* *
* * This program and the accompanying materials are made available under the
* * terms of the Apache License, Version 2.0 which is available at
* * https://www.apache.org/licenses/LICENSE-2.0.
* *
* * See the NOTICE file distributed with this work for additional
* * information regarding copyright ownership.
* * Unless required by applicable law or agreed to in writing, software
* * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* * License for the specific language governing permissions and limitations
* * under the License.
* *
* * SPDX-License-Identifier: Apache-2.0
* *****************************************************************************
*/
package org.nd4j.autodiff.samediff;
import lombok.Data;
import java.util.HashMap;
import java.util.Map;
/**
* Memory usage information for operations
*/
@Data
public class OperationMemoryInfo {
/**
* Memory usage for each input variable (in bytes)
*/
private Map<String, Long> inputMemoryUsage = new HashMap<>();
/**
* Memory usage for each output variable (in bytes)
*/
private Map<String, Long> outputMemoryUsage = new HashMap<>();
/**
* Total memory usage for this operation (in bytes)
*/
private long totalMemoryUsage = 0;
/**
* Peak memory usage observed (in bytes)
*/
private long peakMemoryUsage = 0;
/**
* Memory allocated during operation execution (in bytes)
*/
private long allocatedMemory = 0;
/**
* Memory deallocated after operation execution (in bytes)
*/
private long deallocatedMemory = 0;
/**
* Timestamp when memory usage was last updated
*/
private long lastUpdated = 0;
/**
* Add memory usage for an input variable
*
* @param inputName name of the input variable
* @param memoryUsage memory usage in bytes
*/
public void addInputMemoryUsage(String inputName, long memoryUsage) {
inputMemoryUsage.put(inputName, memoryUsage);
updateTotalMemoryUsage();
}
/**
* Add memory usage for an output variable
*
* @param outputName name of the output variable
* @param memoryUsage memory usage in bytes
*/
public void addOutputMemoryUsage(String outputName, long memoryUsage) {
outputMemoryUsage.put(outputName, memoryUsage);
updateTotalMemoryUsage();
}
/**
* Update total memory usage calculation
*/
public void updateTotalMemoryUsage() {
long inputTotal = inputMemoryUsage.values().stream().mapToLong(Long::longValue).sum();
long outputTotal = outputMemoryUsage.values().stream().mapToLong(Long::longValue).sum();
totalMemoryUsage = inputTotal + outputTotal + allocatedMemory - deallocatedMemory;
peakMemoryUsage = Math.max(peakMemoryUsage, totalMemoryUsage);
lastUpdated = System.currentTimeMillis();
}
/**
* Record memory allocation during operation
*
* @param allocated bytes allocated
*/
public void recordAllocation(long allocated) {
this.allocatedMemory += allocated;
updateTotalMemoryUsage();
}
/**
* Record memory deallocation during operation
*
* @param deallocated bytes deallocated
*/
public void recordDeallocation(long deallocated) {
this.deallocatedMemory += deallocated;
updateTotalMemoryUsage();
}
/**
* Get total input memory usage
*
* @return total memory usage of all inputs in bytes
*/
public long getTotalInputMemoryUsage() {
return inputMemoryUsage.values().stream().mapToLong(Long::longValue).sum();
}
/**
* Get total output memory usage
*
* @return total memory usage of all outputs in bytes
*/
public long getTotalOutputMemoryUsage() {
return outputMemoryUsage.values().stream().mapToLong(Long::longValue).sum();
}
/**
* Get memory usage for a specific input
*
* @param inputName name of the input variable
* @return memory usage in bytes, or 0 if not found
*/
public long getInputMemoryUsage(String inputName) {
return inputMemoryUsage.getOrDefault(inputName, 0L);
}
/**
* Get memory usage for a specific output
*
* @param outputName name of the output variable
* @return memory usage in bytes, or 0 if not found
*/
public long getOutputMemoryUsage(String outputName) {
return outputMemoryUsage.getOrDefault(outputName, 0L);
}
/**
* Check if memory usage is considered high
*
* @param thresholdBytes threshold in bytes
* @return true if total memory usage exceeds threshold
*/
public boolean isHighMemoryUsage(long thresholdBytes) {
return totalMemoryUsage > thresholdBytes;
}
/**
* Get memory usage in MB
*
* @return total memory usage in megabytes
*/
public double getTotalMemoryUsageMB() {
return totalMemoryUsage / (1024.0 * 1024.0);
}
/**
* Get peak memory usage in MB
*
* @return peak memory usage in megabytes
*/
public double getPeakMemoryUsageMB() {
return peakMemoryUsage / (1024.0 * 1024.0);
}
/**
* Get memory efficiency (ratio of current to peak usage)
*
* @return memory efficiency ratio (0.0 to 1.0)
*/
public double getMemoryEfficiency() {
if (peakMemoryUsage == 0) return 1.0;
return (double) totalMemoryUsage / peakMemoryUsage;
}
/**
* Get memory usage summary as formatted string
*
* @return formatted string with memory statistics
*/
public String getMemoryUsageSummary() {
return String.format("Memory: %.2f MB (peak: %.2f MB), Inputs: %.2f MB, Outputs: %.2f MB",
getTotalMemoryUsageMB(),
getPeakMemoryUsageMB(),
getTotalInputMemoryUsage() / (1024.0 * 1024.0),
getTotalOutputMemoryUsage() / (1024.0 * 1024.0));
}
/**
* Reset all memory usage information
*/
public void reset() {
inputMemoryUsage.clear();
outputMemoryUsage.clear();
totalMemoryUsage = 0;
peakMemoryUsage = 0;
allocatedMemory = 0;
deallocatedMemory = 0;
lastUpdated = 0;
}
}
@@ -0,0 +1,187 @@
/*
* ******************************************************************************
* *
* *
* * This program and the accompanying materials are made available under the
* * terms of the Apache License, Version 2.0 which is available at
* * https://www.apache.org/licenses/LICENSE-2.0.
* *
* * See the NOTICE file distributed with this work for additional
* * information regarding copyright ownership.
* * Unless required by applicable law or agreed to in writing, software
* * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* * License for the specific language governing permissions and limitations
* * under the License.
* *
* * SPDX-License-Identifier: Apache-2.0
* *****************************************************************************
*/
package org.nd4j.autodiff.samediff;
import lombok.Data;
import java.util.ArrayList;
import java.util.List;
/**
* Timing information for operation execution
*/
@Data
public class OperationTimingInfo {
/**
* List of execution times for this operation (in nanoseconds)
*/
private List<Long> executionTimes = new ArrayList<>();
/**
* Total accumulated execution time (in nanoseconds)
*/
private long totalExecutionTime = 0;
/**
* Minimum execution time observed (in nanoseconds)
*/
private long minExecutionTime = Long.MAX_VALUE;
/**
* Maximum execution time observed (in nanoseconds)
*/
private long maxExecutionTime = Long.MIN_VALUE;
/**
* Number of times this operation has been executed
*/
private int executionCount = 0;
/**
* Timestamp of first execution
*/
private long firstExecutionTime = 0;
/**
* Timestamp of last execution
*/
private long lastExecutionTime = 0;
/**
* Add a new execution time measurement
*
* @param executionTime execution time in nanoseconds
*/
public void addExecutionTime(long executionTime) {
executionTimes.add(executionTime);
totalExecutionTime += executionTime;
minExecutionTime = Math.min(minExecutionTime, executionTime);
maxExecutionTime = Math.max(maxExecutionTime, executionTime);
executionCount++;
lastExecutionTime = System.currentTimeMillis();
if (firstExecutionTime == 0) {
firstExecutionTime = lastExecutionTime;
}
// Keep only recent execution times to avoid memory issues
if (executionTimes.size() > 1000) {
executionTimes.remove(0);
}
}
/**
* Get average execution time in nanoseconds
*
* @return average execution time, or 0 if no executions recorded
*/
public double getAverageExecutionTime() {
if (executionCount == 0) return 0.0;
return (double) totalExecutionTime / executionCount;
}
/**
* Get average execution time in milliseconds
*
* @return average execution time in milliseconds
*/
public double getAverageExecutionTimeMs() {
return getAverageExecutionTime() / 1_000_000.0;
}
/**
* Get total execution time in milliseconds
*
* @return total execution time in milliseconds
*/
public double getTotalExecutionTimeMs() {
return totalExecutionTime / 1_000_000.0;
}
/**
* Get minimum execution time in milliseconds
*
* @return minimum execution time in milliseconds
*/
public double getMinExecutionTimeMs() {
if (minExecutionTime == Long.MAX_VALUE) return 0.0;
return minExecutionTime / 1_000_000.0;
}
/**
* Get maximum execution time in milliseconds
*
* @return maximum execution time in milliseconds
*/
public double getMaxExecutionTimeMs() {
if (maxExecutionTime == Long.MIN_VALUE) return 0.0;
return maxExecutionTime / 1_000_000.0;
}
/**
* Check if execution time is abnormally high compared to average
*
* @param threshold multiplier for average (e.g., 3.0 for 3x average)
* @return true if last execution was abnormally high
*/
public boolean isAbnormalExecutionTime(double threshold) {
if (executionTimes.isEmpty()) return false;
double avgTime = getAverageExecutionTime();
if (avgTime <= 0) return false;
long lastExecution = executionTimes.get(executionTimes.size() - 1);
return lastExecution > avgTime * threshold;
}
/**
* Get execution time statistics as a summary string
*
* @return formatted string with timing statistics
*/
public String getTimingStatistics() {
if (executionCount == 0) {
return "No executions recorded";
}
return String.format("Executions: %d, Avg: %.2fms, Min: %.2fms, Max: %.2fms, Total: %.2fms",
executionCount,
getAverageExecutionTimeMs(),
getMinExecutionTimeMs(),
getMaxExecutionTimeMs(),
getTotalExecutionTimeMs());
}
/**
* Reset all timing information
*/
public void reset() {
executionTimes.clear();
totalExecutionTime = 0;
minExecutionTime = Long.MAX_VALUE;
maxExecutionTime = Long.MIN_VALUE;
executionCount = 0;
firstExecutionTime = 0;
lastExecutionTime = 0;
}
}
@@ -0,0 +1,15 @@
package org.nd4j.autodiff.samediff;
import lombok.Data;
@Data
public class PerformanceMetrics {
private long totalMemory;
private long freeMemory;
private long usedMemory;
private long maxMemory;
private long totalVariableMemory;
private long loopExecutionTime;
private double averageIterationTime;
private double iterationsPerSecond;
}
@@ -0,0 +1,15 @@
package org.nd4j.autodiff.samediff;
import lombok.Data;
import java.util.ArrayList;
import java.util.List;
@Data
public class RootCauseAnalysis {
private String primaryCause;
private List<String> contributingFactors = new ArrayList<>();
private List<String> recommendedActions = new ArrayList<>();
private List<String> similarPatternsInHistory = new ArrayList<>();
private double confidenceLevel;
}
@@ -0,0 +1,324 @@
/*
* ******************************************************************************
* *
* *
* * This program and the accompanying materials are made available under the
* * terms of the Apache License, Version 2.0 which is available at
* * https://www.apache.org/licenses/LICENSE-2.0.
* *
* * See the NOTICE file distributed with this work for additional
* * information regarding copyright ownership.
* * Unless required by applicable law or agreed to in writing, software
* * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* * License for the specific language governing permissions and limitations
* * under the License.
* *
* * SPDX-License-Identifier: Apache-2.0
* *****************************************************************************
*/
package org.nd4j.autodiff.samediff;
import lombok.Getter;
import org.nd4j.linalg.exception.ND4JIllegalArgumentException;
/**
* SDIndex is the {@link SameDiff}
* equivalent to {@link org.nd4j.linalg.indexing.INDArrayIndex}
* it uses {@link org.nd4j.linalg.api.ops.impl.shape.StridedSlice} underneath to obtain varying elements.
* It also supports {@link SDVariable} inputs allowing for graph definitions of
* indexing operations.
*
* @author Alex Black
* @author Adam Gibson
*/
@Getter
public class SDIndex {
/**
* Index types include the following:
* 1. all: get all elements of this dimension
* 2. point: get only elements at the particular point in this dimension
* 3. interval: get only elements from a begin point to an end point in the interval
* 4. point input: dynamic version of point
* 5. interval input: dynamic version of interval
*/
public enum IndexType {
ALL,
POINT,
INTERVAL,
//inputs aren't integers/longs but SDVariables
POINT_INPUT,
INTERVAL_INPUT
}
private IndexType indexType = IndexType.ALL;
private long pointIndex;
private SDVariable pointVar;
private boolean pointKeepDim;
private Long intervalBegin = null;
private Long intervalEnd = null;
private SDVariable intervalInputBegin = null;
private SDVariable intervalInputEnd = null;
private SDVariable intervalStrideInput = null;
private Long intervalStrides = 1l;
private boolean inclusive = false;
private SDVariable inclusiveInput = null;
public SDIndex(){}
/**
* Represents all the elements in along this dimension.
* @return
*/
public static SDIndex all(){
return new SDIndex();
}
/**
* Represents all elements at a singular point in this dimension (think row or column)
* Note this is the SDVariable version. For static please use {@link #point(long)}
* @param i the input index
* @return
*/
public static SDIndex point(SDVariable i) {
return point(i,false);
}
/**
* Represents all elements at a singular point in this dimension (think row or column)
* This is a static index
* @param i the input index
* @return
*/
public static SDIndex point(long i) {
SDIndex sdIndex = new SDIndex();
sdIndex.indexType = IndexType.POINT;
sdIndex.pointIndex = i;
sdIndex.pointKeepDim = false;
return sdIndex;
}
/**
* Represents all elements at a singular point in this dimension (think row or column)
* This is a dynamic index
* @param i the input index
* @return
*/
public static SDIndex point(SDVariable i, boolean keepDim) {
SDIndex sdIndex = new SDIndex();
sdIndex.indexType = IndexType.POINT_INPUT;
sdIndex.pointVar = i;
sdIndex.pointKeepDim = keepDim;
return sdIndex;
}
/**
* Represents all elements at a singular point in this dimension (think row or column)
* This is a static index
* @param i the input index
* @return
*/
public static SDIndex point(long i, boolean keepDim) {
SDIndex sdIndex = new SDIndex();
sdIndex.indexType = IndexType.POINT;
sdIndex.pointIndex = i;
sdIndex.pointKeepDim = keepDim;
return sdIndex;
}
/**
* Represents all elements begin to end (think get row from beginning to end)
* Note these are dynamic indices.
* @param begin the begin index
* @param end the end index
* @return
*/
public static SDIndex interval(SDVariable begin, SDVariable end) {
SDIndex sdIndex = new SDIndex();
sdIndex.indexType = IndexType.INTERVAL_INPUT;
sdIndex.intervalInputBegin = begin;
sdIndex.intervalInputEnd = end;
sdIndex.inclusiveInput = begin.getSameDiff().constant(0);
return sdIndex;
}
/**
* Represents all elements begin to end (think get row from beginning to end)
* Note these are static indices.
* @param begin the begin index
* @param end the end index
* @return
*/
public static SDIndex interval(Long begin, Long end) {
return interval(begin,end,false);
}
/**
* Represents all elements begin to end (think get row from beginning to end)
* Note these are static indices.
* @param begin the begin index
* @param end the end index
* @return
*/
public static SDIndex interval(Long begin, Long end,Boolean inclusive) {
SDIndex sdIndex = new SDIndex();
sdIndex.indexType = IndexType.INTERVAL;
if(begin != null) {
sdIndex.intervalBegin = begin.longValue();
}
if(end != null) {
sdIndex.intervalEnd = end.longValue();
}
if(inclusive != null) {
sdIndex.inclusive = inclusive;
} else {
sdIndex.inclusive = false;
}
return sdIndex;
}
/**
* Represents all elements begin to end (think get row from beginning to end)
* Note these are static indices.
* @param begin the begin index
* @param end the end index
* @return
*/
public static SDIndex interval(Integer begin, Integer end) {
SDIndex sdIndex = new SDIndex();
sdIndex.indexType = IndexType.INTERVAL;
if(begin != null) {
sdIndex.intervalBegin = begin.longValue();
}
if(end != null){
sdIndex.intervalEnd = end.longValue();
}
sdIndex.inclusive = false;
return sdIndex;
}
/**
* Represents all elements begin to end (think get row from beginning to end)
* Note these are static indices.
* @param begin the begin index
* @param strides the stride to increment by to end
* @param end the end index
* @return
*/
public static SDIndex interval(Long begin, Long strides, Long end) {
if(strides == 0){
throw new ND4JIllegalArgumentException("Invalid index : strides can not be 0.");
}
SDIndex sdIndex = new SDIndex();
sdIndex.indexType = IndexType.INTERVAL;
sdIndex.intervalBegin = begin;
sdIndex.intervalEnd = end;
sdIndex.intervalStrides = strides;
sdIndex.inclusive = false;
return sdIndex;
}
/**
* Represents all elements begin to end (think get row from beginning to end)
* Note these are static indices.
* @param begin the begin index
* @param strides the stride to increment by to end
* @param end the end index
* @param inclusive whether the index is inclusive or not
* @return
*/
public static SDIndex interval(Long begin, Long strides, Long end,Boolean inclusive) {
if(strides == 0) {
throw new ND4JIllegalArgumentException("Invalid index : strides can not be 0.");
}
SDIndex sdIndex = new SDIndex();
sdIndex.indexType = IndexType.INTERVAL;
sdIndex.intervalBegin = begin;
sdIndex.intervalEnd = end;
sdIndex.intervalStrides = strides;
if(inclusive != null) {
sdIndex.inclusive = inclusive;
} else {
sdIndex.inclusive = false;
}
return sdIndex;
}
/**
* Represents all elements begin to end (think get row from beginning to end)
* Note these are static indices.
* @param begin the begin index
* @param strides the stride to increment by to end
* @param end the end index
* @return
*/
public static SDIndex interval(Integer begin, Integer strides, Integer end) {
return interval(begin.longValue(),strides.longValue(),end.longValue());
}
/**
* Represents all elements begin to end (think get row from beginning to end)
* Note these are static indices.
* @param begin the begin index
* @param strides the stride to increment by to end
* @param end the end index
* @return
*/
public static SDIndex interval(SDVariable begin, SDVariable strides, SDVariable end) {
return interval(begin,strides,end,begin.getSameDiff().constant(false));
}
/**
* Represents all elements begin to end (think get row from beginning to end)
* Note these are static indices.
* @param begin the begin index
* @param strides the stride to increment by to end
* @param end the end index
* @return
*/
public static SDIndex interval(SDVariable begin, SDVariable strides, SDVariable end,SDVariable inclusive) {
SDIndex sdIndex = new SDIndex();
sdIndex.indexType = IndexType.INTERVAL_INPUT;
if(begin != null) {
sdIndex.intervalInputBegin = begin;
}
if(end != null) {
sdIndex.intervalInputEnd = end;
}
if(strides != null) {
sdIndex.intervalStrideInput = strides;
}
if(inclusive != null) {
sdIndex.inclusiveInput = inclusive;
} else {
sdIndex.inclusiveInput = begin.getSameDiff().constant(false);
}
return sdIndex;
}
}
@@ -0,0 +1,33 @@
/*
* ******************************************************************************
* *
* *
* * This program and the accompanying materials are made available under the
* * terms of the Apache License, Version 2.0 which is available at
* * https://www.apache.org/licenses/LICENSE-2.0.
* *
* * See the NOTICE file distributed with this work for additional
* * information regarding copyright ownership.
* * Unless required by applicable law or agreed to in writing, software
* * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* * License for the specific language governing permissions and limitations
* * under the License.
* *
* * SPDX-License-Identifier: Apache-2.0
* *****************************************************************************
*/
package org.nd4j.autodiff.samediff;
public interface SameDiffConditional {
/**
* @param context
* @param body
* @return
*/
SDVariable eval(SameDiff context, SameDiffFunctionDefinition body, SDVariable[] inputVars);
}
@@ -0,0 +1,35 @@
/*
* ******************************************************************************
* *
* *
* * This program and the accompanying materials are made available under the
* * terms of the Apache License, Version 2.0 which is available at
* * https://www.apache.org/licenses/LICENSE-2.0.
* *
* * See the NOTICE file distributed with this work for additional
* * information regarding copyright ownership.
* * Unless required by applicable law or agreed to in writing, software
* * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* * License for the specific language governing permissions and limitations
* * under the License.
* *
* * SPDX-License-Identifier: Apache-2.0
* *****************************************************************************
*/
package org.nd4j.autodiff.samediff;
import org.nd4j.linalg.api.ndarray.INDArray;
import java.util.Map;
public interface SameDiffFunctionDefinition {
/**
* @param inputs
* @param variableInputs
* @return
*/
SDVariable[] define(SameDiff sameDiff, Map<String, INDArray> inputs, SDVariable[] variableInputs);
}
@@ -0,0 +1,25 @@
/*
* ******************************************************************************
* *
* *
* * This program and the accompanying materials are made available under the
* * terms of the Apache License, Version 2.0 which is available at
* * https://www.apache.org/licenses/LICENSE-2.0.
* *
* * See the NOTICE file distributed with this work for additional
* * information regarding copyright ownership.
* * Unless required by applicable law or agreed to in writing, software
* * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* * License for the specific language governing permissions and limitations
* * under the License.
* *
* * SPDX-License-Identifier: Apache-2.0
* *****************************************************************************
*/
package org.nd4j.autodiff.samediff;
public interface SameDiffLambda {
SDVariable[] define(SameDiff sameDiff, SDVariable[] inputs);
}
@@ -0,0 +1,25 @@
/*
* ******************************************************************************
* *
* *
* * This program and the accompanying materials are made available under the
* * terms of the Apache License, Version 2.0 which is available at
* * https://www.apache.org/licenses/LICENSE-2.0.
* *
* * See the NOTICE file distributed with this work for additional
* * information regarding copyright ownership.
* * Unless required by applicable law or agreed to in writing, software
* * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* * License for the specific language governing permissions and limitations
* * under the License.
* *
* * SPDX-License-Identifier: Apache-2.0
* *****************************************************************************
*/
package org.nd4j.autodiff.samediff;
public interface SameDiffNoArgSingleLambda {
SDVariable define(SameDiff sameDiff);
}
@@ -0,0 +1,25 @@
/*
* ******************************************************************************
* *
* *
* * This program and the accompanying materials are made available under the
* * terms of the Apache License, Version 2.0 which is available at
* * https://www.apache.org/licenses/LICENSE-2.0.
* *
* * See the NOTICE file distributed with this work for additional
* * information regarding copyright ownership.
* * Unless required by applicable law or agreed to in writing, software
* * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* * License for the specific language governing permissions and limitations
* * under the License.
* *
* * SPDX-License-Identifier: Apache-2.0
* *****************************************************************************
*/
package org.nd4j.autodiff.samediff;
public interface SameDiffSingleLambda {
SDVariable define(SameDiff sameDiff, SDVariable[] inputs);
}
@@ -0,0 +1,16 @@
package org.nd4j.autodiff.samediff;
import lombok.Data;
import java.util.HashMap;
import java.util.Map;
@Data
public class TerminationPrediction {
private int predictedAtIteration;
private int predictedTerminationIteration;
private double confidence;
private String predictionMethod;
private String reasoning;
private Map<String, Object> evidenceData = new HashMap<>();
}
@@ -0,0 +1,12 @@
package org.nd4j.autodiff.samediff;
public enum TerminationType {
CONDITION_FALSE, // Normal termination via loop condition
CONDITION_TRUE_EXIT, // Exit operation triggered by true condition
SWITCH_TERMINATION, // Switch operation caused termination
ERROR_TERMINATION, // Error/exception during loop
TIMEOUT_TERMINATION, // Maximum iterations exceeded
EARLY_BREAK, // Early termination before expected completion
RESOURCE_EXHAUSTION, // Memory or other resource limits
MANUAL_TERMINATION // Explicitly terminated by user/system
}
@@ -0,0 +1,566 @@
/*
* ******************************************************************************
* *
* *
* * This program and the accompanying materials are made available under the
* * terms of the Apache License, Version 2.0 which is available at
* * https://www.apache.org/licenses/LICENSE-2.0.
* *
* * See the NOTICE file distributed with this work for additional
* * information regarding copyright ownership.
* * Unless required by applicable law or agreed to in writing, software
* * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* * License for the specific language governing permissions and limitations
* * under the License.
* *
* * SPDX-License-Identifier: Apache-2.0
* *****************************************************************************
*/
package org.nd4j.autodiff.samediff;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.NonNull;
import lombok.extern.slf4j.Slf4j;
import org.nd4j.common.base.Preconditions;
import org.nd4j.evaluation.IEvaluation;
import org.nd4j.linalg.api.buffer.DataType;
import org.nd4j.linalg.learning.config.IUpdater;
import org.nd4j.linalg.learning.regularization.L1Regularization;
import org.nd4j.linalg.learning.regularization.L2Regularization;
import org.nd4j.linalg.learning.regularization.Regularization;
import org.nd4j.linalg.learning.regularization.WeightDecay;
import org.nd4j.serde.json.JsonMappers;
import java.io.IOException;
import java.util.*;
@Data
@NoArgsConstructor
@AllArgsConstructor
@Slf4j
public class TrainingConfig {
private IUpdater updater;
private List<Regularization> regularization = new ArrayList<>(); //Regularization for all trainable parameters
private boolean minimize = true;
private List<String> dataSetFeatureMapping;
private List<String> dataSetLabelMapping;
private List<String> dataSetFeatureMaskMapping;
private List<String> dataSetLabelMaskMapping;
private int iterationCount;
private int epochCount;
private DataType initialLossDataType;
private Map<String, List<IEvaluation>> trainEvaluations = new HashMap<>();
private Map<String, Integer> trainEvaluationLabels = new HashMap<>();
private Map<String, List<IEvaluation>> validationEvaluations = new HashMap<>();
private Map<String, Integer> validationEvaluationLabels = new HashMap<>();
/**
* Create a training configuration suitable for training a single input, single output network.<br>
* See also the {@link Builder} for creating a TrainingConfig
*
* @param updater The updater configuration to use
* @param dataSetFeatureMapping The name of the placeholder/variable that should be set using the feature INDArray from the DataSet
* (or the first/only feature from a MultiDataSet). For example, if the network input placeholder was
* called "input" then this should be set to "input"
* @param dataSetLabelMapping The name of the placeholder/variable that should be set using the label INDArray from the DataSet
* (or the first/only feature from a MultiDataSet). For example, if the network input placeholder was
* called "input" then this should be set to "input"
*/
public TrainingConfig(IUpdater updater, List<Regularization> regularization, String dataSetFeatureMapping, String dataSetLabelMapping) {
this(updater, regularization, true, Collections.singletonList(dataSetFeatureMapping), Collections.singletonList(dataSetLabelMapping),
Collections.<String>emptyList(), null,DataType.FLOAT);
}
/**
* Create a training configuration suitable for training both single input/output and multi input/output networks.<br>
* See also the {@link Builder} for creating a TrainingConfig
*
* @param updater The updater configuration to use
* @param regularization Regularization for all trainable parameters;\
* @param minimize Set to true if the loss function should be minimized (usually true). False to maximize
* @param dataSetFeatureMapping The name of the placeholders/variables that should be set using the feature INDArray(s) from the
* DataSet or MultiDataSet. For example, if the network had 2 inputs called "input1" and "input2"
* and the MultiDataSet features should be mapped with {@code MultiDataSet.getFeatures(0)->"input1"}
* and {@code MultiDataSet.getFeatures(1)->"input2"}, then this should be set to {@code List<>("input1", "input2")}.
* @param dataSetLabelMapping As per dataSetFeatureMapping, but for the DataSet/MultiDataSet labels
* @param dataSetFeatureMaskMapping May be null. If non-null, the variables that the MultiDataSet feature mask arrays should be associated with.
* @param dataSetLabelMaskMapping May be null. If non-null, the variables that the MultiDataSet label mask arrays should be associated with.
*/
public TrainingConfig(IUpdater updater, List<Regularization> regularization, boolean minimize, List<String> dataSetFeatureMapping, List<String> dataSetLabelMapping,
List<String> dataSetFeatureMaskMapping, List<String> dataSetLabelMaskMapping, DataType initialLossDataType) {
this.updater = updater;
this.regularization = regularization;
this.minimize = minimize;
this.dataSetFeatureMapping = dataSetFeatureMapping;
this.dataSetLabelMapping = dataSetLabelMapping;
this.dataSetFeatureMaskMapping = dataSetFeatureMaskMapping;
this.dataSetLabelMaskMapping = dataSetLabelMaskMapping;
this.initialLossDataType = initialLossDataType;
}
protected TrainingConfig(IUpdater updater, List<Regularization> regularization, boolean minimize, List<String> dataSetFeatureMapping, List<String> dataSetLabelMapping,
List<String> dataSetFeatureMaskMapping, List<String> dataSetLabelMaskMapping,
Map<String, List<IEvaluation>> trainEvaluations, Map<String, Integer> trainEvaluationLabels,
Map<String, List<IEvaluation>> validationEvaluations, Map<String, Integer> validationEvaluationLabels,DataType initialLossDataType) {
this(updater, regularization, minimize, dataSetFeatureMapping, dataSetLabelMapping, dataSetFeatureMaskMapping, dataSetLabelMaskMapping,initialLossDataType);
this.trainEvaluations = trainEvaluations;
this.trainEvaluationLabels = trainEvaluationLabels;
this.validationEvaluations = validationEvaluations;
this.validationEvaluationLabels = validationEvaluationLabels;
}
/**
* Increment the iteration count by 1
*/
public void incrementIterationCount(){
iterationCount++;
}
/**
* Increment the epoch count by 1
*/
public void incrementEpochCount(){
epochCount++;
}
public static Builder builder(){
return new Builder();
}
/**
* Get the index of the label array that the specified variable is associated with
* @param s Name of the variable
* @return The index of the label variable, or -1 if not found
*/
public int labelIdx(String s){
return dataSetLabelMapping.indexOf(s);
}
public static class Builder {
private IUpdater updater;
private List<Regularization> regularization = new ArrayList<>();
private boolean minimize = true;
private List<String> dataSetFeatureMapping;
private List<String> dataSetLabelMapping;
private List<String> dataSetFeatureMaskMapping;
private List<String> dataSetLabelMaskMapping;
private boolean skipValidation = false;
private boolean markLabelsUnused = false;
private DataType initialLossDataType = DataType.FLOAT;
private Map<String, List<IEvaluation>> trainEvaluations = new HashMap<>();
private Map<String, Integer> trainEvaluationLabels = new HashMap<>();
private Map<String, List<IEvaluation>> validationEvaluations = new HashMap<>();
private Map<String, Integer> validationEvaluationLabels = new HashMap<>();
/**
* Set the initial loss data type, defaults to
* {@link DataType#FLOAT} - when setting a data type for a loss function
* we need a beginning data type to compute the gradients. In order to do so,
* we need to set an initial number of zero that acts as the initial gradient.
* This initial loss data type controls the data type of that number.
* This is critical when wanting more fine grained control over the data types
* used in the training process.
* @param initialLossDataType the initial loss data type
* @return
*/
public Builder initialLossDataType(DataType initialLossDataType) {
this.initialLossDataType = initialLossDataType;
return this;
}
/**
* Set the updater (such as {@link org.nd4j.linalg.learning.config.Adam}, {@link org.nd4j.linalg.learning.config.Nesterovs}
* etc. This is also how the learning rate (or learning rate schedule) is set.
* @param updater Updater to set
*/
public Builder updater(IUpdater updater) {
this.updater = updater;
return this;
}
/**
* Sets the L1 regularization coefficient for all trainable parameters. Must be >= 0.<br>
* See {@link L1Regularization} for more details
* @param l1 L1 regularization coefficient
*/
public Builder l1(double l1) {
Preconditions.checkState(l1 >= 0, "L1 regularization coefficient must be >= 0. Got %s", l1);
removeInstances(this.regularization, L1Regularization.class);
this.regularization.add(new L1Regularization(l1));
return this;
}
/**
Sets the L2 regularization coefficient for all trainable parameters. Must be >= 0.<br>
* <b>Note</b>: Generally, {@link WeightDecay} (set via {@link #weightDecay(double,boolean)} should be preferred to
* L2 regularization. See {@link WeightDecay} javadoc for further details.<br>
* Note: L2 regularization and weight decay usually should not be used together; if any weight decay (or L2) has
* been added for the biases, these will be removed first.
*
* @see #weightDecay(double, boolean)
*/
public Builder l2(double l2){
Preconditions.checkState(l2 >= 0.0, "L2 regularization coefficient must be >= 0. Got %s", l2);
//Check if existing L2 exists; if so, replace it. Also remove weight decay - it doesn't make sense to use both
removeInstances(this.regularization, L2Regularization.class);
if(l2 > 0.0) {
removeInstancesWithWarning(this.regularization, WeightDecay.class, "WeightDecay regularization removed: incompatible with added L2 regularization");
this.regularization.add(new L2Regularization(l2));
}
return this;
}
/**
* Add weight decay regularization for all trainable parameters. See {@link WeightDecay} for more details.<br>
* Note: values set by this method will be applied to all applicable layers in the network, unless a different
* value is explicitly set on a given layer. In other words: values set via this method are used as the default
* value, and can be overridden on a per-layer basis.<br>
*
* @param coefficient Weight decay regularization coefficient
* @param applyLR Whether the learning rate should be multiplied in when performing weight decay updates. See {@link WeightDecay} for more details.
*/
public Builder weightDecay(double coefficient, boolean applyLR) {
//Check if existing weight decay if it exists; if so, replace it. Also remove L2 - it doesn't make sense to use both
removeInstances(this.regularization, WeightDecay.class);
if(coefficient > 0.0) {
removeInstancesWithWarning(this.regularization, L2Regularization.class, "L2 regularization removed: incompatible with added WeightDecay regularization");
this.regularization.add(new WeightDecay(coefficient, applyLR));
}
return this;
}
/**
* Add regularization to all trainable parameters in the network
*
* @param regularizations Regularization type(s) to add
*/
public Builder addRegularization(Regularization... regularizations){
Collections.addAll(this.regularization, regularizations);
return this;
}
/**
* Set the regularization for all trainable parameters in the network.
* Note that if any existing regularization types have been added, they will be removed
*
* @param regularization Regularization type(s) to add
*/
public Builder regularization(Regularization... regularization){
if(regularization == null || regularization.length == 0)
return this;
List<Regularization> r = new ArrayList<>();
Collections.addAll(r, regularization);
return regularization(r);
}
/**
* Set the regularization for all trainable parameters in the network.
* Note that if any existing regularization types have been added, they will be removed
*
* @param regularization Regularization type(s) to add
*/
public Builder regularization(List<Regularization> regularization){
this.regularization = regularization;
return this;
}
/**
* Sets whether the loss function should be minimized (true) or maximized (false).<br>
* The loss function is usually minimized in SGD.<br>
* Default: true.
* @param minimize True to minimize, false to maximize
*/
public Builder minimize(boolean minimize){
this.minimize = minimize;
return this;
}
/**
* Set the name of the placeholders/variables that should be set using the feature INDArray(s) from the
* DataSet or MultiDataSet. For example, if the network had 2 inputs called "input1" and "input2"
* and the MultiDataSet features should be mapped with {@code MultiDataSet.getFeatures(0)->"input1"}
* and {@code MultiDataSet.getFeatures(1)->"input2"}, then this should be set to {@code List<>("input1", "input2")}.
*
* @param dataSetFeatureMapping Name of the variables/placeholders that the feature arrays should be mapped to
*/
public Builder dataSetFeatureMapping(String... dataSetFeatureMapping){
return dataSetFeatureMapping(Arrays.asList(dataSetFeatureMapping));
}
/**
* Set the name of the placeholders/variables that should be set using the feature INDArray(s) from the
* DataSet or MultiDataSet. For example, if the network had 2 inputs called "input1" and "input2"
* and the MultiDataSet features should be mapped with {@code MultiDataSet.getFeatures(0)->"input1"}
* and {@code MultiDataSet.getFeatures(1)->"input2"}, then this should be set to {@code "input1", "input2"}.
*
* @param dataSetFeatureMapping Name of the variables/placeholders that the feature arrays should be mapped to
*/
public Builder dataSetFeatureMapping(List<String> dataSetFeatureMapping){
Preconditions.checkNotNull(dataSetFeatureMapping != null && dataSetFeatureMapping.size() > 0, "No feature mapping was provided");
this.dataSetFeatureMapping = dataSetFeatureMapping;
return this;
}
/**
* Set the name of the placeholders/variables that should be set using the labels INDArray(s) from the
* DataSet or MultiDataSet. For example, if the network had 2 labels called "label1" and "label2"
* and the MultiDataSet labels should be mapped with {@code MultiDataSet.getLabel(0)->"label1"}
* and {@code MultiDataSet.getLabels(1)->"label"}, then this should be set to {@code "label1", "label2"}.
*
* @param dataSetLabelMapping Name of the variables/placeholders that the label arrays should be mapped to
*/
public Builder dataSetLabelMapping(String... dataSetLabelMapping){
return dataSetLabelMapping(Arrays.asList(dataSetLabelMapping));
}
/**
* Set the name of the placeholders/variables that should be set using the labels INDArray(s) from the
* DataSet or MultiDataSet. For example, if the network had 2 labels called "label1" and "label2"
* and the MultiDataSet labels should be mapped with {@code MultiDataSet.getLabel(0)->"label1"}
* and {@code MultiDataSet.getLabels(1)->"label"}, then this should be set to {@code "label1", "label2"}.
*
* @param dataSetLabelMapping Name of the variables/placeholders that the label arrays should be mapped to
*/
public Builder dataSetLabelMapping(List<String> dataSetLabelMapping){
Preconditions.checkNotNull(dataSetLabelMapping != null && dataSetLabelMapping.size() > 0, "No label mapping was provided");
this.dataSetLabelMapping = dataSetLabelMapping;
return this;
}
/**
* Calling this method will mark the label as unused. This is basically a way to turn off label mapping validation in
* TrainingConfig builder, for training models without labels.<br>
* Put another way: usually you need to call {@link #dataSetLabelMapping(String...)} to set labels, this method
* allows you to say that the DataSet/MultiDataSet labels aren't used in training.
*/
public Builder markLabelsUnused(){
this.markLabelsUnused = true;
return this;
}
/**
* See {@link #dataSetFeatureMaskMapping(List)}
*/
public Builder dataSetFeatureMaskMapping(String... dataSetFeatureMaskMapping){
return dataSetFeatureMaskMapping(Arrays.asList(dataSetFeatureMaskMapping));
}
/**
* Set the name of the placeholders/variables that should be set using the feature mask INDArray(s) from the
* DataSet or MultiDataSet. For example, if the network had 2 mask variables called "mask1" and "mask2"
* and the MultiDataSet features masks should be mapped with {@code MultiDataSet.getFeatureMaskArray(0)->"mask1"}
* and {@code MultiDataSet.getFeatureMaskArray(1)->"mask2"}, then this should be set to {@code "mask1", "mask2"}.
*
* @param dataSetFeatureMaskMapping Name of the variables/placeholders that the feature arrays should be mapped to
*/
public Builder dataSetFeatureMaskMapping(List<String> dataSetFeatureMaskMapping){
this.dataSetFeatureMaskMapping = dataSetFeatureMaskMapping;
return this;
}
/**
* See {@link #dataSetLabelMaskMapping(List)}
*/
public Builder dataSetLabelMaskMapping(String... dataSetLabelMaskMapping){
return dataSetLabelMaskMapping(Arrays.asList(dataSetLabelMaskMapping));
}
/**
* Set the name of the placeholders/variables that should be set using the label mask INDArray(s) from the
* DataSet or MultiDataSet. For example, if the network had 2 mask variables called "mask1" and "mask2"
* and the MultiDataSet label masks should be mapped with {@code MultiDataSet.getLabelMaskArray(0)->"mask1"}
* and {@code MultiDataSet.getLabelMaskArray(1)->"mask2"}, then this should be set to {@code "mask1", "mask2"}.
*
* @param dataSetLabelMaskMapping Name of the variables/placeholders that the feature arrays should be mapped to
*/
public Builder dataSetLabelMaskMapping(List<String> dataSetLabelMaskMapping){
this.dataSetLabelMaskMapping = dataSetLabelMaskMapping;
return this;
}
public Builder skipBuilderValidation(boolean skip) {
this.skipValidation = skip;
return this;
}
private void addEvaluations(boolean validation, @NonNull Map<String, List<IEvaluation>> evaluationMap, @NonNull Map<String, Integer> labelMap,
@NonNull String variableName, int labelIndex, @NonNull IEvaluation... evaluations) {
if(evaluationMap.containsKey(variableName) && labelMap.get(variableName) != labelIndex){
String s;
if(validation){
s = "This ListenerEvaluations.Builder already has validation evaluations for ";
} else {
s = "This ListenerEvaluations.Builder already has train evaluations for ";
}
throw new IllegalArgumentException(s + "variable " +
variableName + " with label index " + labelIndex + ". You can't add " +
" evaluations with a different label index. Got label index " + labelIndex);
}
if(evaluationMap.containsKey(variableName)){
evaluationMap.get(variableName).addAll(Arrays.asList(evaluations));
} else {
evaluationMap.put(variableName, Arrays.asList(evaluations));
labelMap.put(variableName, labelIndex);
}
}
/**
* Add requested History training evaluations for a parm/variable.
*
* These evaluations will be reported in the {@link org.nd4j.autodiff.listeners.records.History} object returned by fit.
*
* @param variableName The variable to evaluate
* @param labelIndex The index of the label to evaluate against
* @param evaluations The evaluations to run
*/
public Builder trainEvaluation(@NonNull String variableName, int labelIndex, @NonNull IEvaluation... evaluations){
addEvaluations(false, this.trainEvaluations, this.trainEvaluationLabels, variableName,
labelIndex, evaluations);
return this;
}
/**
* Add requested History training evaluations for a parm/variable.
*
* These evaluations will be reported in the {@link org.nd4j.autodiff.listeners.records.History} object returned by fit.
*
* @param variable The variable to evaluate
* @param labelIndex The index of the label to evaluate against
* @param evaluations The evaluations to run
*/
public Builder trainEvaluation(@NonNull SDVariable variable, int labelIndex, @NonNull IEvaluation... evaluations){
return trainEvaluation(variable.name(), labelIndex, evaluations);
}
/**
* Add requested History validation evaluations for a parm/variable.
*
* These evaluations will be reported in the {@link org.nd4j.autodiff.listeners.records.History} object returned by fit.
*
* @param variableName The variable to evaluate
* @param labelIndex The index of the label to evaluate against
* @param evaluations The evaluations to run
*/
public Builder validationEvaluation(@NonNull String variableName, int labelIndex, @NonNull IEvaluation... evaluations){
addEvaluations(true, this.validationEvaluations, this.validationEvaluationLabels, variableName,
labelIndex, evaluations);
return this;
}
/**
* Add requested History validation evaluations for a parm/variable.
*
* These evaluations will be reported in the {@link org.nd4j.autodiff.listeners.records.History} object returned by fit.
*
* @param variable The variable to evaluate
* @param labelIndex The index of the label to evaluate against
* @param evaluations The evaluations to run
*/
public Builder validationEvaluation(@NonNull SDVariable variable, int labelIndex, @NonNull IEvaluation... evaluations){
return validationEvaluation(variable.name(), labelIndex, evaluations);
}
/**
* Add requested evaluations for a parm/variable, for either training or validation.
*
* These evaluations will be reported in the {@link org.nd4j.autodiff.listeners.records.History} object returned by fit.
*
* @param validation Whether to add these evaluations as validation or training
* @param variableName The variable to evaluate
* @param labelIndex The index of the label to evaluate against
* @param evaluations The evaluations to run
*/
public Builder addEvaluations(boolean validation, @NonNull String variableName, int labelIndex, @NonNull IEvaluation... evaluations){
if(validation){
return validationEvaluation(variableName, labelIndex, evaluations);
} else{
return trainEvaluation(variableName, labelIndex, evaluations);
}
}
public TrainingConfig build(){
if(!skipValidation) {
Preconditions.checkState(updater != null, "Updater (optimizer) must not be null. Use updater(IUpdater) to set an updater");
Preconditions.checkState(dataSetFeatureMapping != null, "No DataSet feature mapping has been provided. A " +
"mapping between DataSet array positions and variables/placeholders must be provided - use dateSetFeatureMapping(...) to set this");
Preconditions.checkState(markLabelsUnused || dataSetLabelMapping != null, "No DataSet label mapping has been provided. A " +
"mapping between DataSet array positions and variables/placeholders must be provided - use dataSetLabelMapping(...) to set this," +
" or use markLabelsUnused() to mark labels as unused (for example, for unsupervised learning)");
Preconditions.checkArgument(trainEvaluations.keySet().equals(trainEvaluationLabels.keySet()),
"Must specify a label index for each train evaluation. Expected: %s, got: %s",
trainEvaluations.keySet(), trainEvaluationLabels.keySet());
Preconditions.checkArgument(validationEvaluations.keySet().equals(validationEvaluationLabels.keySet()),
"Must specify a label index for each validation evaluation. Expected: %s, got: %s",
validationEvaluations.keySet(), validationEvaluationLabels.keySet());
}
return new TrainingConfig(updater, regularization, minimize, dataSetFeatureMapping, dataSetLabelMapping,
dataSetFeatureMaskMapping, dataSetLabelMaskMapping,
trainEvaluations, trainEvaluationLabels, validationEvaluations, validationEvaluationLabels,initialLossDataType);
}
}
/**
* Remove any instances of the specified type from the list.
* This includes any subtypes.
* @param list List. May be null
* @param remove Type of objects to remove
*/
public static void removeInstances(List<?> list, Class<?> remove) {
removeInstancesWithWarning(list, remove, null);
}
public static void removeInstancesWithWarning(List<?> list, Class<?> remove, String warning){
if(list == null || list.isEmpty())
return;
Iterator<?> iter = list.iterator();
while(iter.hasNext()){
Object o = iter.next();
if(remove.isAssignableFrom(o.getClass())){
if(warning != null) {
log.warn(warning);
}
iter.remove();
}
}
}
public String toJson(){
try {
return JsonMappers.getMapper().writeValueAsString(this);
} catch (IOException e){
throw new RuntimeException(e);
}
}
public static TrainingConfig fromJson(@NonNull String json){
try{
return JsonMappers.getMapper().readValue(json, TrainingConfig.class);
} catch (IOException e){
throw new RuntimeException(e);
}
}
}
@@ -0,0 +1,15 @@
package org.nd4j.autodiff.samediff;
import lombok.Data;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Data
public class VariableEvolutionAnalysis {
private Map<String, List<Object>> variableEvolution = new HashMap<>();
private Map<String, VariablePattern> detectedPatterns = new HashMap<>();
private List<ConditionEvaluation> conditionEvaluationHistory = new ArrayList<>();
}
@@ -0,0 +1,39 @@
/*
* ******************************************************************************
* *
* *
* * This program and the accompanying materials are made available under the
* * terms of the Apache License, Version 2.0 which is available at
* * https://www.apache.org/licenses/LICENSE-2.0.
* *
* * See the NOTICE file distributed with this work for additional
* * information regarding copyright ownership.
* * Unless required by applicable law or agreed to in writing, software
* * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* * License for the specific language governing permissions and limitations
* * under the License.
* *
* * SPDX-License-Identifier: Apache-2.0
* *****************************************************************************
*/
package org.nd4j.autodiff.samediff;
import org.nd4j.linalg.api.buffer.DataType;
// Inner classes for holding information
public class VariableInfo {
public final String name;
public final VariableType type;
public final DataType dataType;
public String frame;
public int iteration;
public String parentFrame;
public VariableInfo(String name, VariableType type, DataType dataType) {
this.name = name;
this.type = type;
this.dataType = dataType;
}
}

Some files were not shown because too many files have changed in this diff Show More