This is an automated email from the ASF dual-hosted git repository.
arnabp20 pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/systemds.git
The following commit(s) were added to refs/heads/main by this push:
new 54363d725f [SYSTEMDS-3513] Multi-level reuse for GPU intermediates
54363d725f is described below
commit 54363d725fec08abff276731ee81fe75ed9396df
Author: Arnab Phani <[email protected]>
AuthorDate: Sat Apr 1 12:20:56 2023 +0200
[SYSTEMDS-3513] Multi-level reuse for GPU intermediates
This patch enables multi-level reuse for functions and
statement blocks with GPU intermediate outputs.
Also, we add new tests for GPU.
Closes #1800
---
scripts/builtin/.imputeByMode.dml.swp | Bin 0 -> 12288 bytes
.../apache/sysds/runtime/lineage/LineageCache.java | 16 +++++-
.../sysds/runtime/lineage/LineageCacheEntry.java | 4 +-
.../runtime/lineage/LineageGPUCacheEviction.java | 1 +
.../test/functions/lineage/GPUFullReuseTest.java | 33 ++++++++---
.../lineage/GPULineageCacheEvictionTest.java | 21 ++++---
.../functions/lineage/GPUCacheEviction3.dml | 60 +++++++++++++++++++
.../scripts/functions/lineage/LineageReuseGPU1.dml | 31 ++++++++++
.../scripts/functions/lineage/LineageReuseGPU2.dml | 49 ++++++++++++++++
.../scripts/functions/lineage/LineageReuseGPU3.dml | 46 +++++++++++++++
.../scripts/functions/lineage/LineageReuseGPU4.dml | 64 +++++++++++++++++++++
.../scripts/functions/lineage/LineageTraceGPU3.dml | 46 +++++++++++++++
12 files changed, 352 insertions(+), 19 deletions(-)
diff --git a/scripts/builtin/.imputeByMode.dml.swp
b/scripts/builtin/.imputeByMode.dml.swp
new file mode 100644
index 0000000000..9c85094594
Binary files /dev/null and b/scripts/builtin/.imputeByMode.dml.swp differ
diff --git a/src/main/java/org/apache/sysds/runtime/lineage/LineageCache.java
b/src/main/java/org/apache/sysds/runtime/lineage/LineageCache.java
index eea3ea003d..f007e719b9 100644
--- a/src/main/java/org/apache/sysds/runtime/lineage/LineageCache.java
+++ b/src/main/java/org/apache/sysds/runtime/lineage/LineageCache.java
@@ -170,6 +170,7 @@ public class LineageCache
return reuse;
}
+ // Reuse function and statement block outputs
public static boolean reuse(List<String> outNames, List<DataIdentifier>
outParams,
int numOutputs, LineageItem[] liInputs, String name,
ExecutionContext ec)
{
@@ -216,11 +217,24 @@ public class LineageCache
((MatrixObject)boundValue).acquireModify(e.getMBValue());
((MatrixObject)boundValue).release();
}
- else {
+ else if (e.isGPUObject()) {
+ MetaDataFormat md = new
MetaDataFormat(e.getDataCharacteristics(), FileFormat.BINARY);
+ boundValue = new
MatrixObject(ValueType.FP64, boundVarName, md);
+ //Create a GPUObject with the cached
pointer
+ GPUObject gpuObj = new
GPUObject(ec.getGPUContext(0),
+ ((MatrixObject)boundValue),
e.getGPUPointer());
+ //Set dirty to true, so that it is
later copied to the host for write
+ gpuObj.setDirty(true);
+ ((MatrixObject)
boundValue).setGPUObject(ec.getGPUContext(0), gpuObj);
+ //Increment the live count for this
pointer
+
LineageGPUCacheEviction.incrementLiveCount(e.getGPUPointer());
+ }
+ else if (e.isScalarValue()) {
boundValue = e.getSOValue();
if (boundValue == null &&
e.getCacheStatus() == LineageCacheStatus.NOTCACHED)
return false; //the executing
thread removed this entry from cache
}
+ //TODO: support reusing RDD output of functions
funcOutputs.put(boundVarName, boundValue);
LineageItem orig = e._origItem;
diff --git
a/src/main/java/org/apache/sysds/runtime/lineage/LineageCacheEntry.java
b/src/main/java/org/apache/sysds/runtime/lineage/LineageCacheEntry.java
index 2526fb60b5..0d89dc396d 100644
--- a/src/main/java/org/apache/sysds/runtime/lineage/LineageCacheEntry.java
+++ b/src/main/java/org/apache/sysds/runtime/lineage/LineageCacheEntry.java
@@ -168,11 +168,11 @@ public class LineageCacheEntry {
}
public boolean isMatrixValue() {
- return _dt.isMatrix() && _rddObject == null;
+ return _dt.isMatrix() && _rddObject == null && _gpuPointer ==
null;
}
public boolean isScalarValue() {
- return _dt.isScalar() && _rddObject == null;
+ return _dt.isScalar() && _rddObject == null && _gpuPointer ==
null;
}
public boolean isRDDPersist() {
diff --git
a/src/main/java/org/apache/sysds/runtime/lineage/LineageGPUCacheEviction.java
b/src/main/java/org/apache/sysds/runtime/lineage/LineageGPUCacheEviction.java
index b427f81097..429794a3ce 100644
---
a/src/main/java/org/apache/sysds/runtime/lineage/LineageGPUCacheEviction.java
+++
b/src/main/java/org/apache/sysds/runtime/lineage/LineageGPUCacheEviction.java
@@ -84,6 +84,7 @@ public class LineageGPUCacheEviction
long size = getPointerSize(ptr);
if (!freeQueues.containsKey(size))
freeQueues.put(size, new
TreeSet<>(LineageCacheConfig.LineageCacheComparator));
+ //FIXME: Multiple entries can point to same
pointer due to multi-level reuse
freeQueues.get(size).add(GPUCacheEntries.get(ptr));
}
}
diff --git
a/src/test/java/org/apache/sysds/test/functions/lineage/GPUFullReuseTest.java
b/src/test/java/org/apache/sysds/test/functions/lineage/GPUFullReuseTest.java
index 3d16c70dc0..1fcd0766cf 100644
---
a/src/test/java/org/apache/sysds/test/functions/lineage/GPUFullReuseTest.java
+++
b/src/test/java/org/apache/sysds/test/functions/lineage/GPUFullReuseTest.java
@@ -24,10 +24,13 @@ import java.util.HashMap;
import java.util.List;
import org.apache.sysds.runtime.lineage.Lineage;
+import org.apache.sysds.runtime.lineage.LineageCacheConfig;
+import org.apache.sysds.runtime.lineage.LineageCacheStatistics;
import org.apache.sysds.runtime.matrix.data.MatrixValue;
import org.apache.sysds.test.AutomatedTestBase;
import org.apache.sysds.test.TestConfiguration;
import org.apache.sysds.test.TestUtils;
+import org.junit.Assert;
import org.junit.Assume;
import org.junit.BeforeClass;
import org.junit.Test;
@@ -37,8 +40,8 @@ import jcuda.runtime.cudaError;
public class GPUFullReuseTest extends AutomatedTestBase{
protected static final String TEST_DIR = "functions/lineage/";
- protected static final String TEST_NAME1 = "FullReuseGPU1";
- protected static final String TEST_NAME2 = "LineageTraceGPU1";
+ protected static final String TEST_NAME = "LineageReuseGPU";
+ protected static final int TEST_VARIANTS = 4;
protected String TEST_CLASS_DIR = TEST_DIR +
GPUFullReuseTest.class.getSimpleName() + "/";
@BeforeClass
@@ -51,20 +54,30 @@ public class GPUFullReuseTest extends AutomatedTestBase{
@Override
public void setUp() {
TestUtils.clearAssertionInformation();
- addTestConfiguration( TEST_NAME1, new
TestConfiguration(TEST_CLASS_DIR, TEST_NAME1, new String[] {"R"}) );
- addTestConfiguration( TEST_NAME2, new
TestConfiguration(TEST_CLASS_DIR, TEST_NAME2, new String[] {"R"}) );
+ for( int i=1; i<=TEST_VARIANTS; i++ )
+ addTestConfiguration(TEST_NAME+i, new
TestConfiguration(TEST_CLASS_DIR, TEST_NAME+i));
}
@Test
public void ReuseAggBin() { //reuse AggregateBinary and sum
- testLineageTraceExec(TEST_NAME1);
+ testLineageTraceExec(TEST_NAME+"1");
}
@Test
public void ReuseSimpleHLM() { //hyper-parameter tuning over LM
(simple)
- testLineageTraceExec(TEST_NAME2);
+ testLineageTraceExec(TEST_NAME+"2");
}
-
+
+ @Test
+ public void ReuseFunction() { //multi-level reuse for GPU
+ testLineageTraceExec(TEST_NAME+"3");
+ }
+
+ @Test
+ public void ReuseGridSearchLM() { //grid search HO for LM
+ testLineageTraceExec(TEST_NAME+"4");
+ }
+
private void testLineageTraceExec(String testname) {
System.out.println("------------ BEGIN " + testname +
"------------");
getAndLoadTestConfiguration(testname);
@@ -84,7 +97,7 @@ public class GPUFullReuseTest extends AutomatedTestBase{
proArgs.add("-stats");
proArgs.add("-lineage");
- proArgs.add("reuse_full");
+
proArgs.add(LineageCacheConfig.ReuseCacheType.REUSE_MULTILEVEL.name().toLowerCase());
proArgs.add("-args");
proArgs.add(output("R"));
programArgs = proArgs.toArray(new String[proArgs.size()]);
@@ -98,6 +111,10 @@ public class GPUFullReuseTest extends AutomatedTestBase{
//compare results
TestUtils.compareMatrices(R_orig, R_reused, 1e-6, "Origin",
"Reused");
+
+ if( testname.endsWith("3") ) { //function reuse
+ Assert.assertEquals(1L,
LineageCacheStatistics.getMultiLevelFnHits());
+ }
}
}
diff --git
a/src/test/java/org/apache/sysds/test/functions/lineage/GPULineageCacheEvictionTest.java
b/src/test/java/org/apache/sysds/test/functions/lineage/GPULineageCacheEvictionTest.java
index 7cfbbd9dc3..a40eee0316 100644
---
a/src/test/java/org/apache/sysds/test/functions/lineage/GPULineageCacheEvictionTest.java
+++
b/src/test/java/org/apache/sysds/test/functions/lineage/GPULineageCacheEvictionTest.java
@@ -38,8 +38,8 @@ import jcuda.runtime.cudaError;
public class GPULineageCacheEvictionTest extends AutomatedTestBase{
protected static final String TEST_DIR = "functions/lineage/";
- protected static final String TEST_NAME1 = "GPUCacheEviction1";
- protected static final String TEST_NAME2 = "GPUCacheEviction2";
+ protected static final String TEST_NAME = "GPUCacheEviction";
+ protected static final int TEST_VARIANTS = 3;
protected String TEST_CLASS_DIR = TEST_DIR +
GPULineageCacheEvictionTest.class.getSimpleName() + "/";
@BeforeClass
@@ -51,18 +51,23 @@ public class GPULineageCacheEvictionTest extends
AutomatedTestBase{
@Override
public void setUp() {
TestUtils.clearAssertionInformation();
- addTestConfiguration( TEST_NAME1, new
TestConfiguration(TEST_CLASS_DIR, TEST_NAME1, new String[] {"R"}) );
- addTestConfiguration( TEST_NAME2, new
TestConfiguration(TEST_CLASS_DIR, TEST_NAME2, new String[] {"R"}) );
+ for( int i=1; i<=TEST_VARIANTS; i++ )
+ addTestConfiguration(TEST_NAME+i, new
TestConfiguration(TEST_CLASS_DIR, TEST_NAME+i));
}
@Test
- public void eviction() { // generate eviction
- testLineageTraceExec(TEST_NAME2);
+ public void eviction() { //generate eviction
+ testLineageTraceExec(TEST_NAME+"2");
}
@Test
- public void reuseAndEviction() { // reuse and eviction
- testLineageTraceExec(TEST_NAME1);
+ public void reuseAndEviction() { //reuse and eviction
+ testLineageTraceExec(TEST_NAME+"1");
+ }
+
+ @Test
+ public void miniBatchEviction() { //neural network-style workload
+ testLineageTraceExec(TEST_NAME+"3");
}
diff --git a/src/test/scripts/functions/lineage/GPUCacheEviction3.dml
b/src/test/scripts/functions/lineage/GPUCacheEviction3.dml
new file mode 100644
index 0000000000..818b8b2c0a
--- /dev/null
+++ b/src/test/scripts/functions/lineage/GPUCacheEviction3.dml
@@ -0,0 +1,60 @@
+#-------------------------------------------------------------
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# 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.
+#
+#-------------------------------------------------------------
+D = rand(rows=25600, cols=784, min=0, max=20, seed=42)
+bs = 128;
+ep = 10;
+iter_ep = ceil(nrow(D)/bs);
+maxiter = ep * iter_ep;
+beg = 1;
+iter = 0;
+i = 1;
+
+while (iter < maxiter) {
+ end = beg + bs - 1;
+ if (end>nrow(D))
+ end = nrow(D);
+ X = D[beg:end,]
+
+ # reusable OP across epochs
+ X = scale(X, TRUE, TRUE);
+ # pollute cache with not reusable OPs
+ X = ((X + X) * i - X) / (i+1)
+ X = ((X + X) * i - X) / (i+1)
+ X = ((X + X) * i - X) / (i+1)
+ X = ((X + X) * i - X) / (i+1)
+ X = ((X + X) * i - X) / (i+1)
+ X = ((X + X) * i - X) / (i+1)
+ X = ((X + X) * i - X) / (i+1)
+ X = ((X + X) * i - X) / (i+1)
+ X = ((X + X) * i - X) / (i+1)
+ X = ((X + X) * i - X) / (i+1)
+
+ iter = iter + 1;
+ if (end == nrow(D))
+ beg = 1;
+ else
+ beg = end + 1;
+ i = i + 1;
+
+}
+print(sum(X));
+write(X, $1, format="text");
+
diff --git a/src/test/scripts/functions/lineage/LineageReuseGPU1.dml
b/src/test/scripts/functions/lineage/LineageReuseGPU1.dml
new file mode 100644
index 0000000000..9e37a4f53b
--- /dev/null
+++ b/src/test/scripts/functions/lineage/LineageReuseGPU1.dml
@@ -0,0 +1,31 @@
+#-------------------------------------------------------------
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# 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.
+#
+#-------------------------------------------------------------
+X = rand(rows=1000, cols=100, sparsity=1, seed=42);
+y = rand(rows=100, cols=100, sparsity=1, seed=42);
+R = matrix(0, rows=1, cols=100);
+
+for (i in 1:10) {
+ tmp = X %*% y;
+ R[1,i] = sum(tmp);
+}
+
+write(R, $1, format="text");
+
diff --git a/src/test/scripts/functions/lineage/LineageReuseGPU2.dml
b/src/test/scripts/functions/lineage/LineageReuseGPU2.dml
new file mode 100644
index 0000000000..dd223acd05
--- /dev/null
+++ b/src/test/scripts/functions/lineage/LineageReuseGPU2.dml
@@ -0,0 +1,49 @@
+#-------------------------------------------------------------
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# 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.
+#
+#-------------------------------------------------------------
+SimlinRegDS = function(Matrix[Double] X, Matrix[Double] y, Double lamda,
Integer N)
+ return (Matrix[double] beta)
+{
+ A = (t(X) %*% X) + diag(matrix(lamda, rows=N, cols=1));
+ b = t(X) %*% y;
+ beta = solve(A, b);
+}
+
+no_lamda = 10;
+
+stp = (0.1 - 0.0001)/no_lamda;
+lamda = 0.0001;
+lim = 0.1;
+
+X = rand(rows=1000, cols=100, seed=42);
+y = rand(rows=1000, cols=1, seed=42);
+N = ncol(X);
+R = matrix(0, rows=N, cols=no_lamda+2);
+i = 1;
+
+while (lamda < lim)
+{
+ beta = SimlinRegDS(X, y, lamda, N);
+ R[,i] = beta;
+ lamda = lamda + stp;
+ i = i + 1;
+}
+write(R, $1);
+
diff --git a/src/test/scripts/functions/lineage/LineageReuseGPU3.dml
b/src/test/scripts/functions/lineage/LineageReuseGPU3.dml
new file mode 100644
index 0000000000..be15ba2026
--- /dev/null
+++ b/src/test/scripts/functions/lineage/LineageReuseGPU3.dml
@@ -0,0 +1,46 @@
+#-------------------------------------------------------------
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# 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.
+#
+#-------------------------------------------------------------
+
+# Increase rows and cols for better performance gains
+
+SimLM = function(Matrix[Double] X, Matrix[Double] y, Double lamda=0.0001)
return (Matrix[Double] beta)
+{
+ A = t(X) %*% X + diag(matrix(lamda, rows=ncol(X), cols=1));
+ while(FALSE) {}
+ b = t(X) %*% y;
+ beta = cbind(A, b); #gpu
+}
+
+r = 100
+c = 10
+
+X = rand(rows=r, cols=c, seed=42);
+y = rand(rows=r, cols=1, seed=43);
+R = matrix(0, 1, 2);
+
+beta1 = SimLM(X, y, 0.0001);
+R[,1] = sum(beta1);
+
+beta2 = SimLM(X, y, 0.0001);
+R[,2] = sum(beta2); #function reuse
+
+write(R, $1, format="text");
+
diff --git a/src/test/scripts/functions/lineage/LineageReuseGPU4.dml
b/src/test/scripts/functions/lineage/LineageReuseGPU4.dml
new file mode 100644
index 0000000000..0f4de81b34
--- /dev/null
+++ b/src/test/scripts/functions/lineage/LineageReuseGPU4.dml
@@ -0,0 +1,64 @@
+#-------------------------------------------------------------
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# 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.
+#
+#-------------------------------------------------------------
+
+l2norm = function(Matrix[Double] X, Matrix[Double] y, Matrix[Double] B,
Integer icpt)
+return (Matrix[Double] loss) {
+ if (icpt > 0)
+ X = cbind(X, matrix(1, nrow(X), 1));
+ loss = as.matrix(sum((y - X%*%B)^2));
+}
+
+randColSet = function(Matrix[Double] X, Integer seed, Double sample) return
(Matrix[Double] Xi) {
+ temp = rand(rows=ncol(X), cols=1, min = 0, max = 1, sparsity=1, seed=seed)
<= sample
+ Xi = removeEmpty(target = X, margin = "cols", select = temp);
+}
+
+X = rand(rows=100, cols=100, sparsity=1.0, seed=1);
+y = rand(rows=100, cols=1, sparsity=1.0, seed=1);
+
+Rbeta = matrix(0, rows=525, cols=ncol(X)); #nrows = 5*5*3*7 = 525
+Rloss = matrix(0, rows=525, cols=1);
+k = 1;
+for (i in 1:5)
+{
+ #randomly select 15% columns in every iteration
+ Xi = randColSet(X, i, 0.15);
+
+ for (h1 in -4:0) { #reg - values:10^-4 to 10^0
+ for (h2 in 0:2) { #icpt - range: 0, 1, 2
+ for (h3 in -12:-6) { #tol -values: 10^-12 to 10^-6
+ reg = 10^h1;
+ icpt = h2;
+ tol = 10^h3;
+ beta = lm(X=Xi, y=y, icpt=icpt, reg=reg, tol=tol, maxi=0,
verbose=FALSE);
+ Rbeta[k, 1:nrow(beta)] = t(beta);
+ Rloss[k,] = l2norm(Xi, y, beta, icpt);
+ k = k + 1;
+ }
+ }
+ }
+}
+
+while(FALSE) {}
+leastLoss = rowIndexMin(t(Rloss));
+bestModel = Rbeta[as.scalar(leastLoss),];
+write(bestModel, $1, format="text");
+
diff --git a/src/test/scripts/functions/lineage/LineageTraceGPU3.dml
b/src/test/scripts/functions/lineage/LineageTraceGPU3.dml
new file mode 100644
index 0000000000..be15ba2026
--- /dev/null
+++ b/src/test/scripts/functions/lineage/LineageTraceGPU3.dml
@@ -0,0 +1,46 @@
+#-------------------------------------------------------------
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# 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.
+#
+#-------------------------------------------------------------
+
+# Increase rows and cols for better performance gains
+
+SimLM = function(Matrix[Double] X, Matrix[Double] y, Double lamda=0.0001)
return (Matrix[Double] beta)
+{
+ A = t(X) %*% X + diag(matrix(lamda, rows=ncol(X), cols=1));
+ while(FALSE) {}
+ b = t(X) %*% y;
+ beta = cbind(A, b); #gpu
+}
+
+r = 100
+c = 10
+
+X = rand(rows=r, cols=c, seed=42);
+y = rand(rows=r, cols=1, seed=43);
+R = matrix(0, 1, 2);
+
+beta1 = SimLM(X, y, 0.0001);
+R[,1] = sum(beta1);
+
+beta2 = SimLM(X, y, 0.0001);
+R[,2] = sum(beta2); #function reuse
+
+write(R, $1, format="text");
+