diff --git a/.github/workflows/cmake.yml b/.github/workflows/cmake.yml index 509cd206..3255493e 100644 --- a/.github/workflows/cmake.yml +++ b/.github/workflows/cmake.yml @@ -45,4 +45,5 @@ jobs: # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail run: | ./cpp_test "--success" - ./sketch_test "--success" \ No newline at end of file + ./sketch_test "--success" + ./crs_test "--success" \ No newline at end of file diff --git a/benchmark/src/Benchmark.cpp b/benchmark/src/Benchmark.cpp index a47aabec..82093ee9 100644 --- a/benchmark/src/Benchmark.cpp +++ b/benchmark/src/Benchmark.cpp @@ -9,13 +9,36 @@ using namespace std; using namespace INTELLI; using namespace torch; +using namespace DIVERSE_METER; void runSingleThreadTest(std::string configName) { + MeterTable meterTable; + AbstractMeterPtr eMeter = nullptr; ConfigMapPtr cfg = newConfigMap(); cfg->fromFile(configName); AMMBench::MatrixLoaderTable mLoaderTable; uint64_t sketchDimension; sketchDimension = cfg->tryU64("sketchDimension", 50, true); uint64_t coreBind = cfg->tryU64("coreBind", 0, true); + uint64_t usingMeter = cfg->tryU64("usingMeter", 0, true); + std::string meterTag = cfg->tryString("meterTag", "intelMsr", true); + if (usingMeter) { + eMeter = meterTable.findMeter(meterTag); + if (eMeter != nullptr) { + eMeter->setConfig(cfg); + double staticPower = cfg->tryDouble("staticPower", 0.0, false); + if (staticPower == 0.0) { + eMeter->testStaticPower(2); + } else { + INTELLI_INFO("use pre-defined static power"); + eMeter->setStaticPower(staticPower); + } + INTELLI_INFO("static power is " + to_string(eMeter->getStaticPower()) + " W"); + } else { + INTELLI_ERROR("No meter found: " + meterTag); + } + + } + UtilityFunctions::bind2Core((int) coreBind); torch::set_num_threads(1); std::string ptFile = cfg->tryString("ptFile", "torchscripts/FDAMM.pt", true); @@ -33,19 +56,34 @@ void runSingleThreadTest(std::string configName) { matLoaderPtr->setConfig(cfg); auto A = matLoaderPtr->getA(); auto B = matLoaderPtr->getB(); + //555 /*torch::manual_seed(114514); //555 auto A = torch::rand({(long) aRow, (long) aCol}); auto B = torch::rand({(long) aCol, (long) bCol});*/ INTELLI_INFO("Generation done, conducting..."); + if (eMeter != nullptr) { + eMeter->startMeter(); + } ThreadPerf pef((int) coreBind); pef.setPerfList(); pef.start(); auto C =module.forward({A, B, (long) sketchDimension}).toTensor(); pef.end(); + if (eMeter != nullptr) { + eMeter->stopMeter(); + } std::string ruName = "default"; auto resultCsv = pef.resultToConfigMap(); + if (eMeter != nullptr) { + eMeter->stopMeter(); + double energyConsumption = eMeter->getE(); + double staticEnergyConsumption = eMeter->getStaicEnergyConsumption(resultCsv->tryU64("perfElapsedTime", 0, false)); + double pureEnergy = energyConsumption - staticEnergyConsumption; + resultCsv->edit("energyAll", (double) energyConsumption); + resultCsv->edit("energyOnlyMe", (double) pureEnergy); + } resultCsv->toFile(ruName + ".csv"); INTELLI_INFO("Done. here is result"); std::cout << resultCsv->toString() << endl; diff --git a/benchmark/torchscripts/BernoulliCRS.pt b/benchmark/torchscripts/BernoulliCRS.pt new file mode 100644 index 00000000..e635a5f3 Binary files /dev/null and b/benchmark/torchscripts/BernoulliCRS.pt differ diff --git a/benchmark/torchscripts/BernoulliCRS.py b/benchmark/torchscripts/BernoulliCRS.py new file mode 100644 index 00000000..25b5859a --- /dev/null +++ b/benchmark/torchscripts/BernoulliCRS.py @@ -0,0 +1,66 @@ +import torch +import time +import os + +def get_first_element(tensor): + if tensor.numel() == 1: + return tensor.item() + else: + return tensor[0].item() + +def is_empty_tensor(tensor): + return tensor.numel() == 0 + +@torch.jit.script +def BernoulliCRS(A: torch.Tensor, B: torch.Tensor, k: int): + # Get the dimension of A + A = A.t() + n, m = A.shape + + assert n == B.shape[0] + assert k < n + + # probability distribution + sample = torch.rand(n) # default: uniform + sample = torch.div(sample, sample.sum() / k) # sum = k as per the paper + + # diagonal scaling matrix P (nxn) + P = torch.diag(1.0 / torch.sqrt(sample)) + + # random diagonal sampling matrix K (nxn) + sample = (torch.rand(n) < sample).float() + K = torch.diag(sample) + + a = torch.matmul(torch.matmul(A.t(), P), K) + b = torch.matmul(torch.matmul(a, K), P) + + return torch.matmul(b, B) + + +def main(): + + width = 1000 + A = torch.rand(2000, width) + B = torch.rand(width, 2000) + + t = time.time() + + aResult = BernoulliCRS(A, B, 500) + print("approximate: " + str(time.time() - t) + "s") + + print(aResult) + + # exact result + t = time.time() + eResult = torch.matmul(A, B) + print("\nExact: " + str(time.time() - t) + "s") + + print(eResult) + + print("\nerror: " + str(torch.norm(aResult - eResult, p='fro').item())) + + script = BernoulliCRS.save("BernoulliCRS.pt") + +if __name__ == '__main__': + main() + \ No newline at end of file diff --git a/benchmark/torchscripts/CRS.pt b/benchmark/torchscripts/CRS.pt new file mode 100644 index 00000000..b405bcb9 Binary files /dev/null and b/benchmark/torchscripts/CRS.pt differ diff --git a/benchmark/torchscripts/CRSV2.pt b/benchmark/torchscripts/CRSV2.pt new file mode 100644 index 00000000..12de4027 Binary files /dev/null and b/benchmark/torchscripts/CRSV2.pt differ diff --git a/benchmark/torchscripts/ColumnRowSampling.py b/benchmark/torchscripts/ColumnRowSampling.py new file mode 100644 index 00000000..9e7d57c3 --- /dev/null +++ b/benchmark/torchscripts/ColumnRowSampling.py @@ -0,0 +1,70 @@ +import torch +import time +import os +import math + +def get_first_element(tensor): + if tensor.numel() == 1: + return tensor.item() + else: + return tensor[0].item() + +def is_empty_tensor(tensor): + return tensor.numel() == 0 + +@torch.jit.script +def CRS(A: torch.Tensor, B: torch.Tensor, k: int): + # Get the dimension of A + A = A.t() + n, m = A.shape + + assert n == B.shape[0] + assert k < n + + # probability distribution + probs = torch.ones(n) / n # default: uniform + + # sample k indices from range 0 to n for given probability distribution + indices = torch.multinomial(probs, k, replacement=True) + + # Sample k columns from A + A_sampled = A[indices, :] + ratio = math.ceil(n / k) + A_sampled = torch.div((A_sampled / k).t(), probs[::ratio]) + + # Sample k rows from B + B_sampled = B[indices, :] + + # Compute the matrix product + result = A_sampled.matmul(B_sampled) + return result + + +def main(): + + width = 2000 + A = torch.rand(5000, width) + B = torch.rand(width, 5000) + + + t = time.time() + + aResult = CRS(A, B, 500) + print("approximate: " + str(time.time() - t) + "s") + + print(aResult) + + # exact result + t = time.time() + eResult = torch.matmul(A, B) + print("\nExact: " + str(time.time() - t) + "s") + + print(eResult) + + print("\nerror: " + str(torch.norm(aResult - eResult, p='fro').item())) + + FDAMM_script = CRS.save("CRS.pt") + +if __name__ == '__main__': + main() + \ No newline at end of file diff --git a/benchmark/torchscripts/ColumnRowSamplingVer2.py b/benchmark/torchscripts/ColumnRowSamplingVer2.py new file mode 100644 index 00000000..ef67b4e2 --- /dev/null +++ b/benchmark/torchscripts/ColumnRowSamplingVer2.py @@ -0,0 +1,71 @@ +import torch +import time +import os + +def get_first_element(tensor): + if tensor.numel() == 1: + return tensor.item() + else: + return tensor[0].item() + +def is_empty_tensor(tensor): + return tensor.numel() == 0 + +@torch.jit.script +def CRS(A: torch.Tensor, B: torch.Tensor, k: int): + # Get the dimension of A + A = A.t() + n, m = A.shape + + assert n == B.shape[0] + assert k < n + + # probability distribution + # dist = torch.distributions.Uniform(0, 1) + sample = torch.rand(n) # default: uniform + + # diagonal scaling matrix D (nxn) + sample = torch.div(sample, sample.sum()) + D = torch.diag(1.0 / torch.sqrt(k * sample)) + + # sampling matrix S (kxn) + column_indices = torch.multinomial(sample, k, replacement=True) + S = torch.zeros(k, n) + for row, col in enumerate(column_indices): + S[row, col] = 1 + + a = torch.matmul(torch.matmul(A.t(), D), S.t()) + b = torch.matmul(torch.matmul(a, S), D) + + + return torch.matmul(b, B) + + +def main(): + + + width = 2000 + A = torch.rand(1000, width) + B = torch.rand(width, 1000) + + t = time.time() + + aResult = CRS(A, B, 500) + print("approximate: " + str(time.time() - t) + "s") + + print(aResult) + + # exact result + t = time.time() + eResult = torch.matmul(A, B) + print("\nExact: " + str(time.time() - t) + "s") + + print(eResult) + + print("\nerror: " + str(torch.norm(aResult - eResult, p='fro').item())) + + script = CRS.save("CRSV2.pt") + +if __name__ == '__main__': + main() + \ No newline at end of file diff --git a/benchmark/torchscripts/E2E_Minist.py b/benchmark/torchscripts/E2E_Minist.py index 234b5f45..3f3da3d8 100644 --- a/benchmark/torchscripts/E2E_Minist.py +++ b/benchmark/torchscripts/E2E_Minist.py @@ -5,18 +5,15 @@ import torchvision.transforms as transforms import os import CoOccurringFD - - # Define your custom matrix multiplication function def my_matmul(x, y): - rows, cols = x.shape - if cols > 20: - sketchSize = cols / 10 + rows,cols=x.shape + if cols>20: + sketchSize=cols/10 else: - sketchSize = 10 + sketchSize=10 # Your implementation here - return CoOccurringFD.FDAMM(x, y, int(sketchSize)) - + return CoOccurringFD.FDAMM(x,y,int(sketchSize)) # Define a custom Linear layer that uses your custom matrix multiplication function class CustomLinear(nn.Module): @@ -30,11 +27,9 @@ def __init__(self, in_features, out_features, bias=True): else: self.register_parameter('bias', None) self.reset_parameters() - - def mySqrt(self, a: float): + def mySqrt(self,a:float): y = torch.sqrt(torch.tensor(a, dtype=torch.float32)) return y.item() - def reset_parameters(self): nn.init.kaiming_uniform_(self.weight, a=self.mySqrt(5.0)) if self.bias is not None: @@ -49,7 +44,6 @@ def forward(self, input): output += self.bias return output - # Define your neural network architecture class MyNet(nn.Module): def __init__(self): @@ -57,41 +51,36 @@ def __init__(self): self.fc1 = nn.Linear(784, 128) self.fc2 = nn.Linear(128, 128) self.fc3 = nn.Linear(128, 10) - def forward(self, x): x = x.view(-1, 784) x = nn.functional.relu(self.fc1(x)) x = self.fc2(x) x = nn.functional.relu(self.fc3(x)) return x - - -def testNN(net, test_loader): - # first, load parameters - pretrained_params = torch.load('pretrained_model.pt') - custom_params = net.state_dict() - - for name in custom_params: - if name in pretrained_params: - custom_params[name] = pretrained_params[name] - net.load_state_dict(custom_params) - correct = 0 - total = 0 - # then, run test - net2 = net - for data in test_loader: - images, labels = data - outputs = net2(images) - _, predicted = torch.max(outputs.data, 1) - total += labels.size(0) - correct += (predicted == labels).sum().item() +def testNN(net,test_loader): + #first, load parameters + pretrained_params = torch.load('pretrained_model.pt') + custom_params = net.state_dict() + + for name in custom_params: + if name in pretrained_params: + custom_params[name] = pretrained_params[name] + net.load_state_dict(custom_params) + correct = 0 + total = 0 + #then, run test + net2=net + for data in test_loader: + images, labels = data + outputs = net2(images) + _, predicted = torch.max(outputs.data, 1) + total += labels.size(0) + correct += (predicted == labels).sum().item() + print(f"Accuracy on test set: {correct / total}") print(f"Accuracy on test set: {correct / total}") - print(f"Accuracy on test set: {correct / total}") - return correct / total - - + return correct / total def main(): - device = 'cuda' + device='cuda' # Load the MNIST dataset train_dataset = datasets.MNIST(root='./data', train=True, transform=transforms.ToTensor(), download=True) test_dataset = datasets.MNIST(root='./data', train=False, transform=transforms.ToTensor()) @@ -107,48 +96,51 @@ def main(): if os.path.exists('pretrained_model.pt'): print('find pretrained model, run test') print('first run default version') - accuracy0 = testNN(net, test_loader) + accuracy0=testNN(net,test_loader) print('then run coocuuring 1 version') # Replace the Linear layers with your custom Linear layers and load the pre-trained weights net.fc1 = CustomLinear(784, 128) - accuracy1 = testNN(net, test_loader) + accuracy1=testNN(net,test_loader) print('next run coocuuring 2 version') - net.fc1 = nn.Linear(784, 128) + net.fc1=nn.Linear(784,128) net.fc2 = CustomLinear(128, 128) - accuracy2 = testNN(net, test_loader) + accuracy2=testNN(net,test_loader) print('finally run coocuuring 3 version') net.fc2 = nn.Linear(128, 128) net.fc3 = CustomLinear(128, 10) - accuracy3 = testNN(net, test_loader) - print('default accuracy=', accuracy0) - print('co-occuring 1 accuracy=', accuracy1) - print('co-occuring 2 accuracy=', accuracy2) - print('co-occuring 3 accuracy=', accuracy3) - - + accuracy3=testNN(net,test_loader) + print('default accuracy=',accuracy0) + print('co-occuring 1 accuracy=',accuracy1) + print('co-occuring 2 accuracy=',accuracy2) + print('co-occuring 3 accuracy=',accuracy3) + + else: print('build pretrain model first') criterion = nn.CrossEntropyLoss() optimizer = optim.SGD(net.parameters(), lr=0.1) - net = net.to(device) + net=net.to(device) for epoch in range(10): running_loss = 0.0 for i, data in enumerate(train_loader, 0): inputs, labels = data - inputs = inputs.to(device) - labels = labels.to(device) + inputs=inputs.to(device) + labels=labels.to(device) optimizer.zero_grad() outputs = net(inputs) loss = criterion(outputs, labels) loss.backward() optimizer.step() running_loss += loss.item() - print(f"Epoch {epoch + 1}: loss = {running_loss / len(train_loader)}") - net = net.to('cpu') + print(f"Epoch {epoch+1}: loss = {running_loss / len(train_loader)}") + net=net.to('cpu') # Save the pre-trained model torch.save(net.state_dict(), 'pretrained_model.pt') + + + # Evaluate if __name__ == '__main__': - main() + main() \ No newline at end of file diff --git a/commit.sh b/commit.sh index 19788286..5a6df3b4 100755 --- a/commit.sh +++ b/commit.sh @@ -1,4 +1,4 @@ -BRANCH=SYSTEST +BRANCH=METER_CRS git init git checkout -b $BRANCH git add . diff --git a/commit_info b/commit_info index ea5f45f9..391bc0c6 100644 --- a/commit_info +++ b/commit_info @@ -1,2 +1,2 @@ -1. simplify the cmake of test - +1. add the energy meter subsystem +2. include haolan's crs update diff --git a/include/AMMBench.h b/include/AMMBench.h index 4b39c821..6ab70a6e 100755 --- a/include/AMMBench.h +++ b/include/AMMBench.h @@ -20,14 +20,20 @@ * - aRow (U64) the rows of tensor A, required by @ref RandomMatrixLoader, @ref SparseMatrixLoader * - aCol (U64) the columns of tensor A, required by @ref RandomMatrixLoader, @ref SparseMatrixLoader * - bCol (U64) the columns of tensor B, required by @ref RandomMatrixLoader, @ref SparseMatrixLoader - * - "aDensity" The density factor of matrix A, Double, 1.0, required by @ref SparseMatrixLoader - * - "bDensity" The density factor of matrix B, Double, 1.0, required by @ref SparseMatrixLoader - * - "aReduce" Reduce some rows of A to be linearly dependent, U64, 0, required by @ref SparseMatrixLoader - * - "bReduce" Reduce some rows of A to be linearly dependent, U64, 0, required by @ref SparseMatrixLoader + * - aDensity The density factor of matrix A, Double, 1.0, required by @ref SparseMatrixLoader + * - bDensity The density factor of matrix B, Double, 1.0, required by @ref SparseMatrixLoader + * - aReduce Reduce some rows of A to be linearly dependent, U64, 0, required by @ref SparseMatrixLoader + * - "bReduce Reduce some rows of A to be linearly dependent, U64, 0, required by @ref SparseMatrixLoader * - sketchDimension (U64) the dimension of sketch matrix, default 50 * - coreBind (U64) the specific core tor run this benchmark, default 0 * - ptFile (String) the path for the *.pt to be loaded, default torchscripts/FDAMM.pt * - matrixLoaderTag (String) the nameTag of matrix loader, see @ref MatrixLoaderTable, default is random + * @note Additional tags for energy measurement (please validate usingMeter first) see also @ref INTELLI_UTIL_METER + * - usingMeter (U64) set to 1 if you want to use some energy meter, default diabled + * - meterTag (String) the tag of meter, see also @ref MeterTable, default is intelMsr + * - staticPower (Double) set this to >0 if you want to manually config the static power of the device + * - meterAddress (String) set this to the file system path of the meter, if it is different from the meter's default + * @warning For some platforms, the staticPower automatically measured by sleep is not accurate. Please do this mannulally. See also the template config.csv * @section subsec_extend_operator How to extend a new algorithm * - go to the benchmark/torchscripts @@ -71,6 +77,7 @@ * @{ */ #include +#include /** * @ingroup INTELLI_UTIL * @defgroup INTELLI_UTIL_OTHERC20 Other common class or package under C++20 standard diff --git a/include/Utils/Meters/AbstractMeter.hpp b/include/Utils/Meters/AbstractMeter.hpp new file mode 100644 index 00000000..3035caf7 --- /dev/null +++ b/include/Utils/Meters/AbstractMeter.hpp @@ -0,0 +1,125 @@ +/*! \file AbstractMeter.hpp*/ +#ifndef ADB_INCLUDE_UTILS_AbstractMeter_HPP_ +#define ADB_INCLUDE_UTILS_AbstractMeter_HPP_ +//#include +#include +#include +#include +#include +#define METER_ERROR(n) INTELLI_ERROR(n) + +#include +using namespace std; +namespace DIVERSE_METER { +/** + * @ingroup INTELLI_UTIL + * @{ +* @defgroup INTELLI_UTIL_METER Energy Meter packs +* @{ + * This package is used for energy meter +*/ +/** + * @ingroup INTELLI_UTIL_METER + * @class AbstractMeter Utils/Meters/AbstractMeter.hpp + * @brief The abstract class for all meters + * @note default behaviors: + * - create + * - call @ref setConfig() to config this meter + * - (optional) call @ref testStaticPower() to automatically test the static power of a device or @ref setStaticPower to manually set the static power, if you want to exclude it + * - call @ref startMeter() to start measurement + * - (run your program) + * - call @ref stopMeter() to stop measurement + * - call @ref getE(), @ref getPeak(), etc to get the measurement resluts + * + */ +class AbstractMeter { + protected: + /** + * @brief static power of a system in W + */ + double staticPower = 0; + INTELLI::ConfigMapPtr cfg = nullptr; + + private: + + public: + AbstractMeter(/* args */) { + + } + //if exist in another name + + ~AbstractMeter() { + + } + /** + * @brief to set the configmap + * @param cfg the config map + */ + virtual void setConfig(INTELLI::ConfigMapPtr _cfg) { + cfg = _cfg; + } + /** + * @brief to manually set the static power + * @param _sp + */ + void setStaticPower(double _sp) { + staticPower = _sp; + } + /** + * @brief to test the static power of a system by sleeping + * @param sleepingSecond The seconds for sleep + */ + void testStaticPower(uint64_t sleepingSecond); + /** + * @brief to start the meter into some measuring tasks + */ + virtual void startMeter() { + + } + /** + * @brief to stop the meter into some measuring tasks + */ + virtual void stopMeter() { + + } + //energy in J + /** + * @brief to get the energy in J, including static energy consumption of system + */ + virtual double getE() { + return 0.0; + } + + /** + * @brief to get the peak power in W, including static power of system + */ + virtual double getPeak() { + return 0.0; + } + + virtual bool isValid() { + return false; + } + /** + * @brief to return the tested static power + * return the @ref staticPower + */ + double getStaticPower(); + /** +* @brief to return the static energy consumption of a system under several us + * @param runningUs The time in us of a running + * return the @ref staticPower +*/ + double getStaicEnergyConsumption(uint64_t runningUs); + +}; +typedef std::shared_ptr AbstractMeterPtr; +/** + * @} + */ +/** + * @} + */ +} + +#endif \ No newline at end of file diff --git a/include/Utils/Meters/EspMeterUart/EspMeterUart.hpp b/include/Utils/Meters/EspMeterUart/EspMeterUart.hpp new file mode 100644 index 00000000..9b6d8612 --- /dev/null +++ b/include/Utils/Meters/EspMeterUart/EspMeterUart.hpp @@ -0,0 +1,70 @@ +/*! \file EspMeterUart.hpp*/ +#ifndef ADB_INCLUDE_UTILS_EspMeterUartUARY_HPP_ +#define ADB_INCLUDE_UTILS_EspMeterUartUART_HPP_ +//#include + +#include +//#include +using namespace std; +namespace DIVERSE_METER { + +/** + * @ingroup INTELLI_UTIL_METER + * @class EspMeterUart Utils/Meters/EspMeterUart.hpp + * @brief the entity of an esp32s2-based power meter, connected by uart 115200 + * @note default behaviors: + * - create + * - call @ref setConfig() to config this meter + * - (optional) call @ref testStaticPower() to test the static power of a device, if you want to exclude it + * - call @ref startMeter() to start measurement + * - (run your program) + * - call @ref stopMeter() to stop measurement + * - call @ref getE(), @ref getPeak(), etc to get the measurement resluts + * @note config parameters: + * - meterAddress, String, The file system path of meter, default "/dev/ttyUSB0"; + * @note tag is "espUart" + */ +class EspMeterUart : public AbstractMeter { + private: + int devFd = -1; + /** + * @brief The file system path of meter + */ + std::string meterAddress = "/dev/ttyUSB0"; + void openUartDev(); + // uint64_t accessEsp32(uint64_t cmd); + public: + EspMeterUart(/* args */); + ~EspMeterUart(); + /** + * @brief to set the configmap + * @param cfg the config map + */ + virtual void setConfig(INTELLI::ConfigMapPtr _cfg); + /** + * @brief to start the meter into some measuring tasks + */ + void startMeter(); + /** + * @brief to stop the meter into some measuring tasks + */ + void stopMeter(); + /** +* @brief to get the energy in J, including static energy consumption of system +*/ + double getE(); + //peak power in mW + /** + * @brief to get the peak power in W, including static power of system + */ + double getPeak(); + + bool isValid() { + return (devFd != -1); + } +}; +typedef std::shared_ptr EspMeterUartPtr; +#define newEspMeterUart() std::make_shared(); +} + +#endif \ No newline at end of file diff --git a/include/Utils/Meters/IntelMeter/IntelMeter.hpp b/include/Utils/Meters/IntelMeter/IntelMeter.hpp new file mode 100644 index 00000000..98aab374 --- /dev/null +++ b/include/Utils/Meters/IntelMeter/IntelMeter.hpp @@ -0,0 +1,84 @@ +#ifndef ADB_INCLUDE_UTILS_IntelMeter_HPP_ +#define ADB_INCLUDE_UTILS_IntelMeter_HPP_ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +using namespace std; +namespace DIVERSE_METER { +typedef struct rapl_power_unit { + double PU; //power units + double ESU; //energy status units + double TU; //time units +} rapl_power_unit; +/*class:IntelMeter +description:the entity of intel msr-based power meter, providing all function including: +E,PeakPower +note: the meter and bus rate is about 1ms, you must run on intel x64 with modprobe msr and cpuid +date:20211202 +*/ + +/** + * @ingroup INTELLI_UTIL_METER + * @class IntelMeter Utils/Meters/IntelMeter.hpp + * @brief the entity of intel msr-based power meter, may be not support for some newer architectures + * - create + * - call @ref setConfig() to config this meter + * - (optional) call @ref testStaticPower() to test the static power of a device, if you want to exclude it + * - call @ref startMeter() to start measurement + * - (run your program) + * - call @ref stopMeter() to stop measurement + * - call @ref getE(), @ref getPeak(), etc to get the measurement resluts + * @warning: only works for some x64 machines + * @note: no peak power support, tag is "intelMsr" + */ +class IntelMeter : public AbstractMeter { + private: + int devFd; + uint64_t rdmsr(int cpu, uint32_t reg); + rapl_power_unit get_rapl_power_unit(); + double eSum = 0; + + uint32_t maxCpu = 0; + vector cpus; + vector st; + vector en; + vector count; + rapl_power_unit power_units; + public: + /** +* @brief to set the configmap +* @param cfg the config map +*/ + virtual void setConfig(INTELLI::ConfigMapPtr _cfg); + IntelMeter(/* args */); + ~IntelMeter(); + void startMeter(); + void stopMeter(); + //energy in J + double getE(); + //peak power in mW + // double getPeak(); + + bool isValid() { + return (devFd != -1); + } +}; +typedef std::shared_ptr IntelMeterPtr; +#define newIntelMeter() std::make_shared(); +} + +#endif \ No newline at end of file diff --git a/include/Utils/Meters/MeterTable.h b/include/Utils/Meters/MeterTable.h new file mode 100644 index 00000000..6237fa07 --- /dev/null +++ b/include/Utils/Meters/MeterTable.h @@ -0,0 +1,74 @@ +/*! \file MeterTable.hpp*/ +#ifndef INTELLISTREAM_UTILS_METERTABLE_H_ +#define INTELLISTREAM_UTILS_METERTABLE_H_ +#include +#include +namespace DIVERSE_METER { + +/** + * @ingroup INTELLI_UTIL_METER + * @class MeterTable Utils/Meter/MeterTable.h + * @brief The table class to index all meters + * @note Default behavior +* - create +* - (optional) call @ref registerNewMeter for new meter +* - find a loader by @ref findMeter using its tag + * @note default tags + * - espUart @ref EspMeterUart + * - intelMsr @ref IntelMeter + */ +class MeterTable { + protected: + std::map meterMap; + public: + /** + * @brief The constructing function + * @note If new MatrixLoader wants to be included by default, please revise the following in *.cpp + */ + MeterTable(); + + ~MeterTable() { + } + + /** + * @brief To register a new meter + * @param onew The new operator + * @param tag THe name tag + */ + void registerNewMeter(DIVERSE_METER::AbstractMeterPtr dnew, std::string tag) { + meterMap[tag] = dnew; + } + + /** + * @brief find a meter in the table according to its name + * @param name The nameTag of loader + * @return The Meter, nullptr if not found + */ + DIVERSE_METER::AbstractMeterPtr findMeter(std::string name) { + if (meterMap.count(name)) { + return meterMap[name]; + } + return nullptr; + } + /** + * @ingroup INTELLI_UTIL_METER + * @typedef MeterTablePtr + * @brief The class to describe a shared pointer to @ref MeterTable + + */ + typedef std::shared_ptr MeterTablePtr; +/** + * @ingroup INTELLI_UTIL_METER + * @def newMeterTable + * @brief (Macro) To creat a new @ref MeterTable under shared pointer. + */ +#define newMeterTable std::make_shared +}; +} +/** + * @} + */ + + + +#endif //INTELLISTREAM_INCLUDE_MATRIXLOADER_MeterTable_H_ diff --git a/src/Utils/CMakeLists.txt b/src/Utils/CMakeLists.txt index 597ed0ff..3dcb886a 100644 --- a/src/Utils/CMakeLists.txt +++ b/src/Utils/CMakeLists.txt @@ -1,4 +1,5 @@ add_sources( IntelliLog.cpp UtilityFunctions.cpp -) \ No newline at end of file +) +add_subdirectory(Meters) \ No newline at end of file diff --git a/src/Utils/Meters/AbstractMeter.cpp b/src/Utils/Meters/AbstractMeter.cpp new file mode 100644 index 00000000..7ac4feab --- /dev/null +++ b/src/Utils/Meters/AbstractMeter.cpp @@ -0,0 +1,16 @@ +#include +void DIVERSE_METER::AbstractMeter::testStaticPower(uint64_t sleepingSecond) { + startMeter(); + sleep(sleepingSecond); + stopMeter(); + staticPower = getE(); + staticPower = staticPower / sleepingSecond; +} +double DIVERSE_METER::AbstractMeter::getStaicEnergyConsumption(uint64_t runningUs) { + double t = runningUs; + t = t * staticPower / 1e6; + return t; +} +double DIVERSE_METER::AbstractMeter::getStaticPower() { + return staticPower; +} \ No newline at end of file diff --git a/src/Utils/Meters/CMakeLists.txt b/src/Utils/Meters/CMakeLists.txt new file mode 100644 index 00000000..2c1b191b --- /dev/null +++ b/src/Utils/Meters/CMakeLists.txt @@ -0,0 +1,3 @@ +add_sources(AbstractMeter.cpp MeterTable.cpp) +add_subdirectory(EspMeterUart) +add_subdirectory(IntelMeter) diff --git a/src/Utils/Meters/EspMeterUart/CMakeLists.txt b/src/Utils/Meters/EspMeterUart/CMakeLists.txt new file mode 100644 index 00000000..6d680aef --- /dev/null +++ b/src/Utils/Meters/EspMeterUart/CMakeLists.txt @@ -0,0 +1 @@ +add_sources(EspMeterUart.cpp) diff --git a/src/Utils/Meters/EspMeterUart/EspMeterUart.cpp b/src/Utils/Meters/EspMeterUart/EspMeterUart.cpp new file mode 100644 index 00000000..ba7c2b6f --- /dev/null +++ b/src/Utils/Meters/EspMeterUart/EspMeterUart.cpp @@ -0,0 +1,129 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace DIVERSE_METER; +enum { + UART_VCMD_START = 1, + UART_VCMD_STOP, + UART_VCMD_I, + UART_VCMD_V, + UART_VCMD_P, + UART_VCMD_E, + UART_VCMD_PEAK, + +}; +void EspMeterUart::setConfig(INTELLI::ConfigMapPtr _cfg) { + AbstractMeter::setConfig(_cfg); + meterAddress = cfg->tryString("meterAddress", "/dev/ttyUSB0", true); + // openUartDev(); +} +void EspMeterUart::openUartDev() { + devFd = open(meterAddress.data(), O_RDWR | O_NOCTTY); + if (devFd == -1) { + + METER_ERROR("can not open device meter"); + } + //char *welcome="hello world"; + struct termios termios_p; + tcgetattr(devFd, &termios_p); + /** + * @brief set up uart, 115200, maximum compatability + */ + termios_p.c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR | IGNCR | ICRNL | IXON); + termios_p.c_oflag &= ~OPOST; + termios_p.c_lflag &= ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN); + termios_p.c_cflag &= ~(CSIZE | PARENB); + termios_p.c_cflag = B115200 | CS8 | CLOCAL | CREAD; + termios_p.c_cc[VTIME] = 0; + termios_p.c_cc[VMIN] = 1; + tcsetattr(devFd, TCSANOW, &termios_p); + tcflush(devFd, TCIOFLUSH); + fcntl(devFd, F_SETFL, O_NONBLOCK); + //close(devFd); +} +EspMeterUart::EspMeterUart(/* args */) { + +} +/* +EspMeterUart::EspMeterUart(string name) { + devFd = open(name.data(), O_RDWR); + if (devFd == -1) { + + METER_ERROR("can not open device meter"); + } +}*/ +EspMeterUart::~EspMeterUart() { + /*if (devFd != -1) { + close(devFd); + }*/ +} +void EspMeterUart::startMeter() { + openUartDev(); + uint8_t cmdSend = UART_VCMD_START; + //double ru=0; + write(devFd, &cmdSend, 1); + close(devFd); +} +void EspMeterUart::stopMeter() { + openUartDev(); + uint8_t cmdSend = UART_VCMD_STOP; + //double ru=0; + write(devFd, &cmdSend, 1); + close(devFd); +} +double EspMeterUart::getE() { + openUartDev(); + double ru = 0; + uint8_t cmdSend = UART_VCMD_E; + write(devFd, &cmdSend, 1); +//usleep(1000); +//double ru; + int ret = -1; + uint64_t tryCnt = 0; + while (ret < 0 && tryCnt < 1000) { + ret = read(devFd, &ru, sizeof(double)); + tryCnt++; + usleep(1000); + } + close(devFd); + return ru; +} + +double EspMeterUart::getPeak() { + double ru = 0; + uint8_t cmdSend = UART_VCMD_PEAK; + write(devFd, &cmdSend, 1); +//usleep(1000); +//double ru; + int ret = -1; + uint64_t tryCnt = 0; + while (ret < 0 && tryCnt < 1000) { + ret = read(devFd, &ru, sizeof(double)); + tryCnt++; + usleep(1000); + } + return ru; +} \ No newline at end of file diff --git a/src/Utils/Meters/IntelMeter/CMakeLists.txt b/src/Utils/Meters/IntelMeter/CMakeLists.txt new file mode 100644 index 00000000..64fe7c21 --- /dev/null +++ b/src/Utils/Meters/IntelMeter/CMakeLists.txt @@ -0,0 +1,2 @@ + +add_sources(IntelMeter.cpp) diff --git a/src/Utils/Meters/IntelMeter/IntelMeter.cpp b/src/Utils/Meters/IntelMeter/IntelMeter.cpp new file mode 100644 index 00000000..42a5a1c4 --- /dev/null +++ b/src/Utils/Meters/IntelMeter/IntelMeter.cpp @@ -0,0 +1,93 @@ +#include +#include +using namespace DIVERSE_METER; + +IntelMeter::IntelMeter(/* args */) { + //system("modprobe cpuid\r\n"); + //system("modprobe msr\r\n"); + + //en=vector(maxCpu); +} +void IntelMeter::setConfig(INTELLI::ConfigMapPtr _cfg) { + AbstractMeter::setConfig(_cfg); + maxCpu = std::thread::hardware_concurrency(); + power_units = get_rapl_power_unit(); + uint32_t i; + //printf("we have %d ,%dcores\r\n",maxCpu,i); + cpus = vector(maxCpu); + st = vector(maxCpu); + en = vector(maxCpu); + count = vector(maxCpu); + + for (i = 0; i < maxCpu; i++) { + cpus[i] = i; + } +} +IntelMeter::~IntelMeter() { + +} +uint64_t IntelMeter::rdmsr(int cpu, uint32_t reg) { + char buf[1024]; + sprintf(buf, "/dev/cpu/%d/msr", cpu); + int msr_file = open(buf, O_RDONLY); + if (msr_file < 0) { + perror("rdmsr: open"); + return msr_file; + } + uint64_t data; + if (pread(msr_file, &data, sizeof(data), reg) != sizeof(data)) { + fprintf(stderr, "read msr register 0x%x error.\n", reg); + perror("rdmsr: read msr"); + return -1; + } + close(msr_file); + return data; +} +rapl_power_unit IntelMeter::get_rapl_power_unit() { + rapl_power_unit ret; + uint64_t data = rdmsr(0, 0x606); + double t = (1 << (data & 0xf)); + t = 1.0 / t; + ret.PU = t; + t = (1 << ((data >> 8) & 0x1f)); + ret.ESU = 1.0 / t; + t = (1 << ((data >> 16) & 0xf)); + ret.TU = 1.0 / t; + return ret; +} +void IntelMeter::startMeter() { + double energy_units = power_units.ESU; + uint32_t cpu, i; + uint64_t data; + size_t n = st.size(); + for (i = 0; i < n; ++i) { + cpu = cpus[i]; + data = rdmsr(cpu, 0x611); + st[i] = (data & 0xffffffff) * energy_units; + } +} +void IntelMeter::stopMeter() { + double energy_units = power_units.ESU; + uint32_t cpu, i; + uint64_t data; + size_t n = st.size(); + eSum = 0; + for (i = 0; i < n; ++i) { + cpu = cpus[i]; + data = rdmsr(cpu, 0x611); + en[i] = (data & 0xffffffff) * energy_units; + count[i] = 0; + if (en[i] < st[i]) { + count[i] = (double) (1ll << 32) + en[i] - st[i]; + } else { + count[i] = en[i] - st[i]; + } + eSum += count[i]; + } + +} +double IntelMeter::getE() { + + return eSum / 1000.0; +} + diff --git a/src/Utils/Meters/MeterTable.cpp b/src/Utils/Meters/MeterTable.cpp new file mode 100644 index 00000000..0a51a4d9 --- /dev/null +++ b/src/Utils/Meters/MeterTable.cpp @@ -0,0 +1,13 @@ +#include +#include +#include +namespace DIVERSE_METER { +/** + * @note revise me if you need new loader + */ +DIVERSE_METER::MeterTable::MeterTable() { + meterMap["espUart"] = newEspMeterUart(); + meterMap["intelMsr"] = newIntelMeter(); +} + +} \ No newline at end of file diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 96fa3cd5..f1fa57db 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -20,6 +20,7 @@ macro(add_catch_test appName SOURCE_FILES SOURCE_LIBS) endmacro() add_catch_test(cpp_test SystemTest/SimpleTest.cpp IntelliStream) add_catch_test(sketch_test SystemTest/SketchTest.cpp IntelliStream) +add_catch_test(crs_test SystemTest/CRSTest.cpp IntelliStream) diff --git a/test/SystemTest/CRSTest.cpp b/test/SystemTest/CRSTest.cpp new file mode 100644 index 00000000..f2afbf87 --- /dev/null +++ b/test/SystemTest/CRSTest.cpp @@ -0,0 +1,63 @@ +#include + +#define CATCH_CONFIG_MAIN +#include "catch.hpp" +#include +using namespace std; +using namespace INTELLI; +using namespace torch; +void runSingleThreadTest(std::string configName) { + ConfigMapPtr cfg = newConfigMap(); + cfg->fromFile(configName); + AMMBench::MatrixLoaderTable mLoaderTable; + uint64_t sketchDimension; + sketchDimension = cfg->tryU64("sketchDimension", 50, true); + uint64_t coreBind = cfg->tryU64("coreBind", 0, true); + UtilityFunctions::bind2Core((int) coreBind); + torch::set_num_threads(1); + std::string ptFile = cfg->tryString("ptFile", "torchscripts/FDAMM.pt", true); + + //uint64_t customResultName = cfg->tryU64("customResultName", 0, true); + INTELLI_INFO("Place me at core" + to_string(coreBind)); + INTELLI_INFO( + "with sketch" + to_string(sketchDimension)); + torch::jit::script::Module module; + INTELLI_INFO("Try pt file " + ptFile); + module = torch::jit::load(ptFile); + std::string matrixLoaderTag = cfg->tryString("matrixLoaderTag", "random", true); + auto matLoaderPtr = mLoaderTable.findMatrixLoader(matrixLoaderTag); + assert(matLoaderPtr); + matLoaderPtr->setConfig(cfg); + auto A = matLoaderPtr->getA(); + auto B = matLoaderPtr->getB(); + /*torch::manual_seed(114514); +//555 +auto A = torch::rand({(long) aRow, (long) aCol}); +auto B = torch::rand({(long) aCol, (long) bCol});*/ + INTELLI_INFO("Generation done, conducting..."); + ThreadPerf pef((int) coreBind); + pef.setPerfList(); + pef.start(); + auto C =module.forward({A, B, (long) sketchDimension}).toTensor(); + pef.end(); + std::string ruName = "default"; + + auto resultCsv = pef.resultToConfigMap(); + resultCsv->toFile(ruName + ".csv"); + INTELLI_INFO("Done. here is result"); + std::cout << resultCsv->toString() << endl; +} +TEST_CASE("Test the COLUMN ROW SAMPLINGS", "[short]") +{ + int a = 0; + runSingleThreadTest("scripts/config_CRS.csv"); + // place your test here + REQUIRE(a == 0); +} +TEST_CASE("Test the COLUMN ROW SAMPLINGS, V2", "[short]") +{ + int a = 0; + runSingleThreadTest("scripts/config_CRSV2.csv"); + // place your test here + REQUIRE(a == 0); +} \ No newline at end of file diff --git a/test/scripts/config_CRS.csv b/test/scripts/config_CRS.csv new file mode 100644 index 00000000..b6afe1ef --- /dev/null +++ b/test/scripts/config_CRS.csv @@ -0,0 +1,6 @@ +key,value,type +aRow,100,U64 +aCol,1000,U64 +bCol,500,U64 +sketchDimension,25,U64 +ptFile,torchscripts/CRS.pt,String \ No newline at end of file diff --git a/test/scripts/config_CRSV2.csv b/test/scripts/config_CRSV2.csv new file mode 100644 index 00000000..a63e79b2 --- /dev/null +++ b/test/scripts/config_CRSV2.csv @@ -0,0 +1,6 @@ +key,value,type +aRow,100,U64 +aCol,1000,U64 +bCol,500,U64 +sketchDimension,25,U64 +ptFile,torchscripts/CRSV2.pt,String \ No newline at end of file diff --git a/test/torchscripts/BernoulliCRS.pt b/test/torchscripts/BernoulliCRS.pt new file mode 100644 index 00000000..e635a5f3 Binary files /dev/null and b/test/torchscripts/BernoulliCRS.pt differ diff --git a/test/torchscripts/BernoulliCRS.py b/test/torchscripts/BernoulliCRS.py new file mode 100644 index 00000000..25b5859a --- /dev/null +++ b/test/torchscripts/BernoulliCRS.py @@ -0,0 +1,66 @@ +import torch +import time +import os + +def get_first_element(tensor): + if tensor.numel() == 1: + return tensor.item() + else: + return tensor[0].item() + +def is_empty_tensor(tensor): + return tensor.numel() == 0 + +@torch.jit.script +def BernoulliCRS(A: torch.Tensor, B: torch.Tensor, k: int): + # Get the dimension of A + A = A.t() + n, m = A.shape + + assert n == B.shape[0] + assert k < n + + # probability distribution + sample = torch.rand(n) # default: uniform + sample = torch.div(sample, sample.sum() / k) # sum = k as per the paper + + # diagonal scaling matrix P (nxn) + P = torch.diag(1.0 / torch.sqrt(sample)) + + # random diagonal sampling matrix K (nxn) + sample = (torch.rand(n) < sample).float() + K = torch.diag(sample) + + a = torch.matmul(torch.matmul(A.t(), P), K) + b = torch.matmul(torch.matmul(a, K), P) + + return torch.matmul(b, B) + + +def main(): + + width = 1000 + A = torch.rand(2000, width) + B = torch.rand(width, 2000) + + t = time.time() + + aResult = BernoulliCRS(A, B, 500) + print("approximate: " + str(time.time() - t) + "s") + + print(aResult) + + # exact result + t = time.time() + eResult = torch.matmul(A, B) + print("\nExact: " + str(time.time() - t) + "s") + + print(eResult) + + print("\nerror: " + str(torch.norm(aResult - eResult, p='fro').item())) + + script = BernoulliCRS.save("BernoulliCRS.pt") + +if __name__ == '__main__': + main() + \ No newline at end of file diff --git a/test/torchscripts/BetaCoOccurringFD.pt b/test/torchscripts/BetaCoOccurringFD.pt new file mode 100644 index 00000000..abd51561 Binary files /dev/null and b/test/torchscripts/BetaCoOccurringFD.pt differ diff --git a/test/torchscripts/BetaCoOccurringFD.py b/test/torchscripts/BetaCoOccurringFD.py new file mode 100644 index 00000000..9060fca8 --- /dev/null +++ b/test/torchscripts/BetaCoOccurringFD.py @@ -0,0 +1,110 @@ +import torch +import time +import os +import math + + +def get_first_element(tensor): + if tensor.numel() == 1: + return tensor.item() + else: + return tensor[0].item() + + +def is_empty_tensor(tensor): + return tensor.numel() == 0 + + +def attenuate(beta: float, k: torch.Tensor, l: int) -> torch.Tensor: + return (torch.exp(k * beta / (l - 1)) - 1) / (torch.exp(beta) - 1) + + +@torch.jit.script +def FDAMM(A: torch.Tensor, B: torch.Tensor, l: int): + B = B.t() + beta = 1.0 + assert A.shape[1] == B.shape[1] + mx, n = A.shape + my, n = B.shape + # initialize sketch matrices + BX = torch.zeros((mx, l)) + BY = torch.zeros((my, l)) + + # the first l iterations + for i in range(l): + BX[:, i] = A[:, i] + BY[:, i] = B[:, i] + + zero_columns = torch.tensor([0]) + zero_columns = zero_columns[1:] + + # iteration l to n: insert if available, else shrink sketch matrices + for i in range(l, n): + # acruire the index of a zero valued column + if len(zero_columns) != 0: + idx = int(get_first_element(zero_columns)) + # assert idx == get_first_element(torch.nonzero(torch.sum(BX, dim = 0) == 0).squeeze()) + BX[:, idx] = A[:, i] + BY[:, idx] = B[:, i] + zero_columns = zero_columns[1:] + + # if no zero valued column, shrink accrodingly + else: + QX, RX = torch.linalg.qr(BX) + QY, RY = torch.linalg.qr(BY) + U, SV, V = torch.svd(torch.matmul(RX, RY.t())) + + # find the median of singular values + S_sorted = torch.sort(SV).values + delta = (S_sorted[len(S_sorted) // 2] if len(S_sorted) % 2 == 1 else + (S_sorted[len(S_sorted) // 2 - 1] + S_sorted[len(S_sorted) // 2]) / 2) + + # delta = S_sorted[-1] + + # parameterizedReduceRank + indices = torch.arange(l, dtype=torch.float32) + attenuated_values = attenuate(beta, indices, l) + parameterizedReduceRank = delta * attenuated_values + SV_shrunk = torch.clamp(SV - parameterizedReduceRank, min=0) + + # restore SV diagnal matrix + SV = torch.diag_embed(SV_shrunk) + SV_sqrt = torch.sqrt(SV) + + # update indices of zero valued columns + zero_indices = torch.nonzero(SV_shrunk == 0).squeeze() + zero_columns = torch.unique(torch.cat((zero_columns, zero_indices))) + + # update sketch matrices + BX = torch.matmul(torch.matmul(QX, U), SV_sqrt) + BY = torch.matmul(torch.matmul(QY, V), SV_sqrt) + + return torch.matmul(BX, BY.t()) + + +def main(): + width = 1000 + A = torch.rand(10000, width) + B = torch.rand(width, 5000) + + t = time.time() + + aResult = FDAMM(A, B, 500) + print("approximate: " + str(time.time() - t) + "s") + + print(aResult) + + # exact result + t = time.time() + eResult = torch.matmul(A, B) + print("\nExact: " + str(time.time() - t) + "s") + + print(eResult) + + print("\nerror: " + str(torch.norm(aResult - eResult, p='fro').item())) + + FDAMM_script = FDAMM.save("BetaCoOccurringFD.pt") + + +if __name__ == '__main__': + main() diff --git a/test/torchscripts/CRS.pt b/test/torchscripts/CRS.pt new file mode 100644 index 00000000..b405bcb9 Binary files /dev/null and b/test/torchscripts/CRS.pt differ diff --git a/test/torchscripts/CRSV2.pt b/test/torchscripts/CRSV2.pt new file mode 100644 index 00000000..12de4027 Binary files /dev/null and b/test/torchscripts/CRSV2.pt differ diff --git a/test/torchscripts/CoOccurringFD.pt b/test/torchscripts/CoOccurringFD.pt new file mode 100644 index 00000000..7631f19a Binary files /dev/null and b/test/torchscripts/CoOccurringFD.pt differ diff --git a/test/torchscripts/CoOccurringFD.py b/test/torchscripts/CoOccurringFD.py new file mode 100644 index 00000000..95b4b403 --- /dev/null +++ b/test/torchscripts/CoOccurringFD.py @@ -0,0 +1,118 @@ +import torch +import time +import os +import math + + +def get_first_element(tensor): + if tensor.numel() == 1: + return tensor.item() + else: + return tensor[0].item() + + +def is_empty_tensor(tensor): + return tensor.numel() == 0 + + +def attenuate(beta, k, l): + return (math.e ** (k * beta / (l - 1)) - 1) / (math.e ** beta - 1) + + +def paramerizedReduceRank(SV, delta, l, beta): + elements = [delta * attenuate(beta, i, l) for i in range(l)] + reduceRank = torch.tensor(elements, dtype=torch.float) + return torch.clamp(SV - reduceRank, min=0) + + +def medianReduceRank(SV, delta): + return torch.clamp(SV - delta, min=0) + + +@torch.jit.script +def FDAMM(A: torch.Tensor, B: torch.Tensor, l: int): + # if beta set zero, the median is used to reduce rank + B = B.t() + + assert A.shape[1] == B.shape[1] + mx, n = A.shape + my, n = B.shape + # initialize sketch matrices + BX = torch.zeros((mx, l)) + BY = torch.zeros((my, l)) + + # the first l iterations + for i in range(l): + BX[:, i] = A[:, i] + BY[:, i] = B[:, i] + + zero_columns = torch.tensor([0]) + zero_columns = zero_columns[1:] + + # iteration l to n: insert if available, else shrink sketch matrices + for i in range(l, n): + # acruire the index of a zero valued column + if len(zero_columns) != 0: + idx = int(get_first_element(zero_columns)) + # assert idx == get_first_element(torch.nonzero(torch.sum(BX, dim = 0) == 0).squeeze()) + BX[:, idx] = A[:, i] + BY[:, idx] = B[:, i] + zero_columns = zero_columns[1:] + + # if no zero valued column, shrink accrodingly + else: + QX, RX = torch.linalg.qr(BX) + QY, RY = torch.linalg.qr(BY) + U, SV, V = torch.svd(torch.matmul(RX, RY.t())) + + # find the median of singular values + S_sorted = torch.sort(SV).values + delta = (S_sorted[len(S_sorted) // 2] if len(S_sorted) % 2 == 1 else + (S_sorted[len(S_sorted) // 2 - 1] + S_sorted[len(S_sorted) // 2]) / 2) + + # delta = S_sorted[-1] + + # shrink the singular values with delta + SV_shrunk = medianReduceRank(SV, delta) + + # restore SV diagnal matrix + SV = torch.diag_embed(SV_shrunk) + SV_sqrt = torch.sqrt(SV) + + # update indices of zero valued columns + zero_indices = torch.nonzero(SV_shrunk == 0).squeeze() + zero_columns = torch.unique(torch.cat((zero_columns, zero_indices))) + + # update sketch matrices + BX = torch.matmul(torch.matmul(QX, U), SV_sqrt) + BY = torch.matmul(torch.matmul(QY, V), SV_sqrt) + + return torch.matmul(BX, BY.t()) + + +def main(): + width = 1000 + A = torch.rand(10000, width) + B = torch.rand(width, 5000) + + t = time.time() + + aResult = FDAMM(A, B, 200) + print("approximate: " + str(time.time() - t) + "s") + + print(aResult) + + # exact result + t = time.time() + eResult = torch.matmul(A, B) + print("\nExact: " + str(time.time() - t) + "s") + + print(eResult) + + print("\nerror: " + str(torch.norm(aResult - eResult, p='fro').item())) + + FDAMM_script = FDAMM.save("CoOccurringFD.pt") + + +if __name__ == '__main__': + main() diff --git a/test/torchscripts/ColumnRowSampling.py b/test/torchscripts/ColumnRowSampling.py new file mode 100644 index 00000000..9e7d57c3 --- /dev/null +++ b/test/torchscripts/ColumnRowSampling.py @@ -0,0 +1,70 @@ +import torch +import time +import os +import math + +def get_first_element(tensor): + if tensor.numel() == 1: + return tensor.item() + else: + return tensor[0].item() + +def is_empty_tensor(tensor): + return tensor.numel() == 0 + +@torch.jit.script +def CRS(A: torch.Tensor, B: torch.Tensor, k: int): + # Get the dimension of A + A = A.t() + n, m = A.shape + + assert n == B.shape[0] + assert k < n + + # probability distribution + probs = torch.ones(n) / n # default: uniform + + # sample k indices from range 0 to n for given probability distribution + indices = torch.multinomial(probs, k, replacement=True) + + # Sample k columns from A + A_sampled = A[indices, :] + ratio = math.ceil(n / k) + A_sampled = torch.div((A_sampled / k).t(), probs[::ratio]) + + # Sample k rows from B + B_sampled = B[indices, :] + + # Compute the matrix product + result = A_sampled.matmul(B_sampled) + return result + + +def main(): + + width = 2000 + A = torch.rand(5000, width) + B = torch.rand(width, 5000) + + + t = time.time() + + aResult = CRS(A, B, 500) + print("approximate: " + str(time.time() - t) + "s") + + print(aResult) + + # exact result + t = time.time() + eResult = torch.matmul(A, B) + print("\nExact: " + str(time.time() - t) + "s") + + print(eResult) + + print("\nerror: " + str(torch.norm(aResult - eResult, p='fro').item())) + + FDAMM_script = CRS.save("CRS.pt") + +if __name__ == '__main__': + main() + \ No newline at end of file diff --git a/test/torchscripts/ColumnRowSamplingVer2.py b/test/torchscripts/ColumnRowSamplingVer2.py new file mode 100644 index 00000000..ef67b4e2 --- /dev/null +++ b/test/torchscripts/ColumnRowSamplingVer2.py @@ -0,0 +1,71 @@ +import torch +import time +import os + +def get_first_element(tensor): + if tensor.numel() == 1: + return tensor.item() + else: + return tensor[0].item() + +def is_empty_tensor(tensor): + return tensor.numel() == 0 + +@torch.jit.script +def CRS(A: torch.Tensor, B: torch.Tensor, k: int): + # Get the dimension of A + A = A.t() + n, m = A.shape + + assert n == B.shape[0] + assert k < n + + # probability distribution + # dist = torch.distributions.Uniform(0, 1) + sample = torch.rand(n) # default: uniform + + # diagonal scaling matrix D (nxn) + sample = torch.div(sample, sample.sum()) + D = torch.diag(1.0 / torch.sqrt(k * sample)) + + # sampling matrix S (kxn) + column_indices = torch.multinomial(sample, k, replacement=True) + S = torch.zeros(k, n) + for row, col in enumerate(column_indices): + S[row, col] = 1 + + a = torch.matmul(torch.matmul(A.t(), D), S.t()) + b = torch.matmul(torch.matmul(a, S), D) + + + return torch.matmul(b, B) + + +def main(): + + + width = 2000 + A = torch.rand(1000, width) + B = torch.rand(width, 1000) + + t = time.time() + + aResult = CRS(A, B, 500) + print("approximate: " + str(time.time() - t) + "s") + + print(aResult) + + # exact result + t = time.time() + eResult = torch.matmul(A, B) + print("\nExact: " + str(time.time() - t) + "s") + + print(eResult) + + print("\nerror: " + str(torch.norm(aResult - eResult, p='fro').item())) + + script = CRS.save("CRSV2.pt") + +if __name__ == '__main__': + main() + \ No newline at end of file diff --git a/test/torchscripts/E2E_Minist.py b/test/torchscripts/E2E_Minist.py new file mode 100644 index 00000000..3f3da3d8 --- /dev/null +++ b/test/torchscripts/E2E_Minist.py @@ -0,0 +1,146 @@ +import torch +import torch.nn as nn +import torch.optim as optim +import torchvision.datasets as datasets +import torchvision.transforms as transforms +import os +import CoOccurringFD +# Define your custom matrix multiplication function +def my_matmul(x, y): + rows,cols=x.shape + if cols>20: + sketchSize=cols/10 + else: + sketchSize=10 + # Your implementation here + return CoOccurringFD.FDAMM(x,y,int(sketchSize)) + +# Define a custom Linear layer that uses your custom matrix multiplication function +class CustomLinear(nn.Module): + def __init__(self, in_features, out_features, bias=True): + super().__init__() + self.in_features = in_features + self.out_features = out_features + self.weight = nn.Parameter(torch.Tensor(out_features, in_features)) + if bias: + self.bias = nn.Parameter(torch.Tensor(out_features)) + else: + self.register_parameter('bias', None) + self.reset_parameters() + def mySqrt(self,a:float): + y = torch.sqrt(torch.tensor(a, dtype=torch.float32)) + return y.item() + def reset_parameters(self): + nn.init.kaiming_uniform_(self.weight, a=self.mySqrt(5.0)) + if self.bias is not None: + fan_in, _ = nn.init._calculate_fan_in_and_fan_out(self.weight) + bound = 1 / self.mySqrt(fan_in) + nn.init.uniform_(self.bias, -bound, bound) + + def forward(self, input): + # Use your custom matrix multiplication function instead of torch.matmul + output = my_matmul(input, self.weight.t()) + if self.bias is not None: + output += self.bias + return output + +# Define your neural network architecture +class MyNet(nn.Module): + def __init__(self): + super(MyNet, self).__init__() + self.fc1 = nn.Linear(784, 128) + self.fc2 = nn.Linear(128, 128) + self.fc3 = nn.Linear(128, 10) + def forward(self, x): + x = x.view(-1, 784) + x = nn.functional.relu(self.fc1(x)) + x = self.fc2(x) + x = nn.functional.relu(self.fc3(x)) + return x +def testNN(net,test_loader): + #first, load parameters + pretrained_params = torch.load('pretrained_model.pt') + custom_params = net.state_dict() + + for name in custom_params: + if name in pretrained_params: + custom_params[name] = pretrained_params[name] + net.load_state_dict(custom_params) + correct = 0 + total = 0 + #then, run test + net2=net + for data in test_loader: + images, labels = data + outputs = net2(images) + _, predicted = torch.max(outputs.data, 1) + total += labels.size(0) + correct += (predicted == labels).sum().item() + print(f"Accuracy on test set: {correct / total}") + print(f"Accuracy on test set: {correct / total}") + return correct / total +def main(): + device='cuda' + # Load the MNIST dataset + train_dataset = datasets.MNIST(root='./data', train=True, transform=transforms.ToTensor(), download=True) + test_dataset = datasets.MNIST(root='./data', train=False, transform=transforms.ToTensor()) + + # Set up the data loaders + batch_size = 64 + train_loader = torch.utils.data.DataLoader(train_dataset, batch_size=batch_size, shuffle=True) + test_loader = torch.utils.data.DataLoader(test_dataset, batch_size=batch_size, shuffle=False) + + # Train the neural network using the default Linear layers + net = MyNet() + + if os.path.exists('pretrained_model.pt'): + print('find pretrained model, run test') + print('first run default version') + accuracy0=testNN(net,test_loader) + print('then run coocuuring 1 version') + # Replace the Linear layers with your custom Linear layers and load the pre-trained weights + net.fc1 = CustomLinear(784, 128) + accuracy1=testNN(net,test_loader) + print('next run coocuuring 2 version') + net.fc1=nn.Linear(784,128) + net.fc2 = CustomLinear(128, 128) + accuracy2=testNN(net,test_loader) + print('finally run coocuuring 3 version') + net.fc2 = nn.Linear(128, 128) + net.fc3 = CustomLinear(128, 10) + accuracy3=testNN(net,test_loader) + print('default accuracy=',accuracy0) + print('co-occuring 1 accuracy=',accuracy1) + print('co-occuring 2 accuracy=',accuracy2) + print('co-occuring 3 accuracy=',accuracy3) + + + else: + print('build pretrain model first') + criterion = nn.CrossEntropyLoss() + optimizer = optim.SGD(net.parameters(), lr=0.1) + net=net.to(device) + for epoch in range(10): + running_loss = 0.0 + for i, data in enumerate(train_loader, 0): + inputs, labels = data + inputs=inputs.to(device) + labels=labels.to(device) + optimizer.zero_grad() + outputs = net(inputs) + loss = criterion(outputs, labels) + loss.backward() + optimizer.step() + running_loss += loss.item() + print(f"Epoch {epoch+1}: loss = {running_loss / len(train_loader)}") + net=net.to('cpu') + # Save the pre-trained model + torch.save(net.state_dict(), 'pretrained_model.pt') + + + + + +# Evaluate +if __name__ == '__main__': + main() \ No newline at end of file diff --git a/test/torchscripts/FDAMM.pt b/test/torchscripts/FDAMM.pt index c320062c..5f6d9fce 100644 Binary files a/test/torchscripts/FDAMM.pt and b/test/torchscripts/FDAMM.pt differ diff --git a/test/torchscripts/FDAMM.py b/test/torchscripts/FDAMM.py new file mode 100644 index 00000000..6dc46068 --- /dev/null +++ b/test/torchscripts/FDAMM.py @@ -0,0 +1,86 @@ +import torch +import time + + +def get_first_element(tensor): + if tensor.numel() == 1: + return tensor.item() + else: + return tensor[0].item() + + +@torch.jit.script +def FDAMM(A: torch.Tensor, B: torch.Tensor, l: int): + # assert A.shape[1] == B.shape[1] + mx, n = A.shape + bn, my = B.shape + # initialize sketch matrices + BX = torch.zeros((mx, l)) + BY = torch.zeros((my, l)) + + for i in range(n): + # find zero valued column, to be replaced with a better mechanism + sum_cols = torch.sum(BX, dim=0) + zero_valued_columns_X = torch.nonzero(sum_cols == 0).squeeze() # sum each column to find the zero valued ones + + # if a zero valued column exists, insert a column + if len(zero_valued_columns_X.shape) != 0: + idx = int(get_first_element(zero_valued_columns_X)) + BX[:, idx] = A[:, i] + + sum_cols = torch.sum(BY, dim=0) + zero_valued_columns_Y = torch.nonzero(sum_cols == 0).squeeze() + if len(zero_valued_columns_Y.shape) != 0: + idx = int(get_first_element(zero_valued_columns_Y)) + BY[:, idx] = B[i, :] + + # if no zero valued column, shrink accrodingly + if len(zero_valued_columns_X.shape) == 0 and len(zero_valued_columns_Y.shape) == 0: + QX, RX = torch.linalg.qr(BX) + QY, RY = torch.linalg.qr(BY) + U, SV, V = torch.svd(torch.matmul(RX, RY.t())) + + # find the median of singular values + S_sorted = torch.sort(SV).values + delta = (S_sorted[len(S_sorted) // 2] if len(S_sorted) % 2 == 1 else + (S_sorted[len(S_sorted) // 2 - 1] + S_sorted[len(S_sorted) // 2]) / 2) + + # shrink the singular values with delta + # (this is based on co-occuring paper from 2017, diffrent from beta-Co-FD) + SV_shrunk = torch.clamp(SV - delta, min=0) + SV = torch.diag_embed(SV_shrunk) + SV_sqrt = torch.sqrt(SV) + + BX = torch.matmul(torch.matmul(QX, U), SV_sqrt) + BY = torch.matmul(torch.matmul(QY, V), SV_sqrt) + + return torch.matmul(BX, BY.t()) + + +@torch.jit.script +def RAWMM(A: torch.Tensor, B: torch.Tensor, l: int): + return torch.matmul(A, B) + + +def main(): + A = torch.rand(10000, 1000) + B = torch.rand(1000, 5000) + # A= A.to('cuda') + # B = B.to('cuda') + t = time.time() + Aresult = FDAMM(A, B, 500) + print("approximate: " + str(time.time() - t) + "s") + print(Aresult) + + t = time.time() + Eresult = torch.matmul(A, B) # exact result + print("\nExact: " + str(time.time() - t) + "s") + print(Eresult) + FDAMM_script = FDAMM.save("FDAMM.pt") + RAWMM_script = RAWMM.save("RAWMM.pt") + + +# print("\nerror: " + str(torch.norm(Aresult - Eresult, p='fro').item())) + +if __name__ == '__main__': + main() diff --git a/test/torchscripts/RAWMM.pt b/test/torchscripts/RAWMM.pt new file mode 100644 index 00000000..6f9bd35e Binary files /dev/null and b/test/torchscripts/RAWMM.pt differ