blob: fae7605d4556de86d2821bf71232de93ed1c0f9c [file]
/*
* 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.
*/
package org.apache.sysds.runtime.codegen;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
import java.util.stream.IntStream;
import org.apache.sysds.runtime.DMLRuntimeException;
import org.apache.sysds.runtime.compress.CompressedMatrixBlock;
import org.apache.sysds.runtime.controlprogram.caching.MatrixObject;
import org.apache.sysds.runtime.data.DenseBlock;
import org.apache.sysds.runtime.data.DenseBlockFactory;
import org.apache.sysds.runtime.data.SparseBlock;
import org.apache.sysds.runtime.data.SparseRow;
import org.apache.sysds.runtime.data.SparseRowVector;
import org.apache.sysds.runtime.instructions.cp.DoubleObject;
import org.apache.sysds.runtime.instructions.cp.ScalarObject;
import org.apache.sysds.runtime.matrix.data.LibMatrixMult;
import org.apache.sysds.runtime.matrix.data.LibMatrixReorg;
import org.apache.sysds.runtime.matrix.data.MatrixBlock;
import org.apache.sysds.runtime.util.CommonThreadPool;
import org.apache.sysds.runtime.util.UtilFunctions;
public abstract class SpoofRowwise extends SpoofOperator
{
private static final long serialVersionUID = 6242910797139642998L;
// Enum with explicit integer values
// Thanks to https://codingexplained.com/coding/java/enum-to-integer-and-integer-to-enum
// these values need to match with their native counterparts (spoof cuda ops)
public enum RowType {
NO_AGG(0), //no aggregation
NO_AGG_B1(1), //no aggregation w/ matrix mult B1
NO_AGG_CONST(2), //no aggregation w/ expansion/contraction
FULL_AGG(3), //full row/col aggregation
ROW_AGG(4), //row aggregation (e.g., rowSums() or X %*% v)
COL_AGG (5), //col aggregation (e.g., colSums() or t(y) %*% X)
COL_AGG_T(6), //transposed col aggregation (e.g., t(X) %*% y)
COL_AGG_B1(7), //col aggregation w/ matrix mult B1
COL_AGG_B1_T(8), //transposed col aggregation w/ matrix mult B1
COL_AGG_B1R(9), //col aggregation w/ matrix mult B1 to row vector
COL_AGG_CONST (10);//col aggregation w/ expansion/contraction
private final int value;
private final static HashMap<Integer, RowType> map = new HashMap<>();
RowType(int value) {
this.value = value;
}
static {
for (RowType rowType : RowType.values()) {
map.put(rowType.value, rowType);
}
}
public static RowType valueOf(int rowType) {
return map.get(rowType);
}
public int getValue() {
return value;
}
public boolean isColumnAgg() {
return this == COL_AGG || this == COL_AGG_T
|| this == COL_AGG_B1 || this == COL_AGG_B1_T
|| this == COL_AGG_B1R || this == COL_AGG_CONST;
}
public boolean isRowTypeB1() {
return this == NO_AGG_B1 || this == COL_AGG_B1
|| this == COL_AGG_B1_T || this == COL_AGG_B1R;
}
public boolean isRowTypeB1ColumnAgg() {
return (this == COL_AGG_B1) || (this == COL_AGG_B1_T);
}
public boolean isConstDim2(long dim2) {
return (this == NO_AGG_CONST || this == COL_AGG_CONST)
|| (dim2>=0 && isRowTypeB1());
}
}
protected final RowType _type;
protected final long _constDim2;
protected final boolean _tB1;
protected final int _reqVectMem;
public SpoofRowwise(RowType type, long constDim2, boolean tB1, int reqVectMem) {
_type = type;
_constDim2 = constDim2;
_tB1 = tB1;
_reqVectMem = reqVectMem;
}
public RowType getRowType() {
return _type;
}
public long getConstDim2() {
return _constDim2;
}
public int getNumIntermediates() {
return _reqVectMem;
}
@Override
public String getSpoofType() {
return "RA" + getClass().getName().split("\\.")[1];
}
@Override public SpoofCUDAOperator createCUDAInstrcution(Integer opID, SpoofCUDAOperator.PrecisionProxy ep) {
return new SpoofCUDARowwise(_type, _constDim2, _tB1, _reqVectMem, opID, ep);
}
@Override
public ScalarObject execute(ArrayList<MatrixBlock> inputs, ArrayList<ScalarObject> scalarObjects, int k) {
MatrixBlock out = ( k > 1 ) ?
execute(inputs, scalarObjects, new MatrixBlock(1,1,false), k) :
execute(inputs, scalarObjects, new MatrixBlock(1,1,false));
return new DoubleObject(out.quickGetValue(0, 0));
}
@Override
public MatrixBlock execute(ArrayList<MatrixBlock> inputs, ArrayList<ScalarObject> scalarObjects, MatrixBlock out) {
return execute(inputs, scalarObjects, out, true, false, 0);
}
public MatrixBlock execute(ArrayList<MatrixBlock> inputs, ArrayList<ScalarObject> scalarObjects, MatrixBlock out, boolean allocTmp, boolean aggIncr, long rix) {
//sanity check
if( inputs==null || inputs.size() < 1 || out==null )
throw new RuntimeException("Invalid input arguments.");
//result allocation and preparations
final int m = inputs.get(0).getNumRows();
final int n = inputs.get(0).getNumColumns();
final int n2 = _type.isConstDim2(_constDim2) ? (int)_constDim2 :
_type.isRowTypeB1() || hasMatrixSideInput(inputs) ?
getMinColsMatrixSideInputs(inputs) : -1;
if( !aggIncr || !out.isAllocated() )
allocateOutputMatrix(m, n, n2, out);
DenseBlock c = out.getDenseBlock();
final boolean flipOut = _type.isRowTypeB1ColumnAgg()
&& LibSpoofPrimitives.isFlipOuter(out.getNumRows(), out.getNumColumns());
//input preparation
SideInput[] b = prepInputMatrices(inputs, 1, inputs.size()-1, false, _tB1);
double[] scalars = prepInputScalars(scalarObjects);
//setup thread-local memory if necessary
if( allocTmp &&_reqVectMem > 0 )
LibSpoofPrimitives.setupThreadLocalMemory(_reqVectMem, n, n2);
//core sequential execute
MatrixBlock a = inputs.get(0);
if(a instanceof CompressedMatrixBlock)
a = CompressedMatrixBlock.getUncompressed(a);
if( !a.isInSparseFormat() )
executeDense(a.getDenseBlock(), b, scalars, c, n, 0, m, rix);
else
executeSparse(a.getSparseBlock(), b, scalars, c, n, 0, m, rix);
//post-processing
if( allocTmp &&_reqVectMem > 0 )
LibSpoofPrimitives.cleanupThreadLocalMemory();
if( flipOut ) {
fixTransposeDimensions(out);
out = LibMatrixReorg.transpose(out, new MatrixBlock(
out.getNumColumns(), out.getNumRows(), false));
}
if( !aggIncr ) {
out.recomputeNonZeros();
out.examSparsity();
}
return out;
}
@Override
public MatrixBlock execute(ArrayList<MatrixBlock> inputs, ArrayList<ScalarObject> scalarObjects, MatrixBlock out, int k)
{
//redirect to serial execution
if( k <= 1 || (_type.isColumnAgg() && !LibMatrixMult.satisfiesMultiThreadingConstraints(inputs.get(0), k))
|| getTotalInputSize(inputs) < PAR_NUMCELL_THRESHOLD ) {
return execute(inputs, scalarObjects, out);
}
//sanity check
if( inputs==null || inputs.size() < 1 || out==null )
throw new RuntimeException("Invalid input arguments.");
//result allocation and preparations
final int m = inputs.get(0).getNumRows();
final int n = inputs.get(0).getNumColumns();
final int n2 = _type.isConstDim2(_constDim2) ? (int)_constDim2 :
_type.isRowTypeB1() || hasMatrixSideInput(inputs) ?
getMinColsMatrixSideInputs(inputs) : -1;
allocateOutputMatrix(m, n, n2, out);
final boolean flipOut = _type.isRowTypeB1ColumnAgg()
&& LibSpoofPrimitives.isFlipOuter(out.getNumRows(), out.getNumColumns());
//input preparation
MatrixBlock a = inputs.get(0);
SideInput[] b = prepInputMatrices(inputs, 1, inputs.size()-1, false, _tB1);
double[] scalars = prepInputScalars(scalarObjects);
//core parallel execute
ExecutorService pool = CommonThreadPool.get(k);
ArrayList<Integer> blklens = UtilFunctions
.getBalancedBlockSizesDefault(m, k, (long)m*n<16*PAR_NUMCELL_THRESHOLD);
try
{
if( _type.isColumnAgg() || _type == RowType.FULL_AGG ) {
//execute tasks
ArrayList<ParColAggTask> tasks = new ArrayList<>();
int outLen = out.getNumRows() * out.getNumColumns();
for( int i=0, lb=0; i<blklens.size(); lb+=blklens.get(i), i++ )
tasks.add(new ParColAggTask(a, b, scalars, n, n2, outLen, lb, lb+blklens.get(i)));
List<Future<DenseBlock>> taskret = pool.invokeAll(tasks);
//aggregate partial results
int len = _type.isColumnAgg() ? out.getNumRows()*out.getNumColumns() : 1;
for( Future<DenseBlock> task : taskret )
LibMatrixMult.vectAdd(task.get().valuesAt(0), out.getDenseBlockValues(), 0, 0, len);
out.recomputeNonZeros();
}
else {
//execute tasks
ArrayList<ParExecTask> tasks = new ArrayList<>();
for( int i=0, lb=0; i<blklens.size(); lb+=blklens.get(i), i++ )
tasks.add(new ParExecTask(a, b, out, scalars, n, n2, lb, lb+blklens.get(i)));
List<Future<Long>> taskret = pool.invokeAll(tasks);
//aggregate nnz, no need to aggregate results
long nnz = 0;
for( Future<Long> task : taskret )
nnz += task.get();
out.setNonZeros(nnz);
}
pool.shutdown();
if( flipOut ) {
fixTransposeDimensions(out);
out = LibMatrixReorg.transpose(out, new MatrixBlock(
out.getNumColumns(), out.getNumRows(), false));
}
out.examSparsity();
}
catch(Exception ex) {
throw new DMLRuntimeException(ex);
}
return out;
}
public static boolean hasMatrixSideInput(ArrayList<MatrixBlock> inputs) {
return IntStream.range(1, inputs.size())
.mapToObj(i -> inputs.get(i))
.anyMatch(in -> in.getNumColumns()>1);
}
protected static int getMinColsMatrixSideInputs(ArrayList<MatrixBlock> inputs) {
//For B1 types, get the output number of columns as the minimum
//number of columns of side input matrices other than vectors.
return IntStream.range(1, inputs.size())
.map(i -> inputs.get(i).getNumColumns())
.filter(ncol -> ncol > 1).min().orElse(1);
}
public static boolean hasMatrixObjectSideInput(ArrayList<MatrixObject> inputs) {
return IntStream.range(1, inputs.size())
.mapToObj(i -> inputs.get(i))
.anyMatch(in -> in.getNumColumns()>1);
}
protected static int getMinColsMatrixObjectSideInputs(ArrayList<MatrixObject> inputs) {
//For B1 types, get the output number of columns as the minimum
//number of columns of side input matrices other than vectors.
return IntStream.range(1, inputs.size())
.map(i -> (int) inputs.get(i).getNumColumns())
.filter(ncol -> ncol > 1).min().orElse(1);
}
protected class OutputDimensions {
public final int rows;
public final int cols;
OutputDimensions(int m, int n, int n2) {
switch(_type) {
case NO_AGG: rows = m; cols = n; break;
case NO_AGG_B1: rows = m; cols = n2; break;
case NO_AGG_CONST: rows = m; cols = (int) SpoofRowwise.this._constDim2; break;
case FULL_AGG: rows = 1; cols = 1; break;
case ROW_AGG: rows = m; cols = 1; break;
case COL_AGG: rows = 1; cols = n; break;
case COL_AGG_T: rows = n; cols = 1; break;
case COL_AGG_B1: rows = n2; cols = n; break;
case COL_AGG_B1_T: rows = n; cols = n2; break;
case COL_AGG_B1R: rows = 1; cols = n2; break;
case COL_AGG_CONST: rows = 1; cols = (int) SpoofRowwise.this._constDim2; break;
default: rows = 0; cols = 0;
}
}
};
private void allocateOutputMatrix(int m, int n, int n2, MatrixBlock out) {
OutputDimensions dims = new OutputDimensions(m, n, n2);
out.reset(dims.rows, dims.cols, false);
out.allocateDenseBlock();
}
private static void fixTransposeDimensions(MatrixBlock out) {
int rlen = out.getNumRows();
out.setNumRows(out.getNumColumns());
out.setNumColumns(rlen);
out.setNonZeros(out.getNumRows()*out.getNumColumns());
}
private void executeDense(DenseBlock a, SideInput[] b, double[] scalars, DenseBlock c, int n, int rl, int ru, long rix) {
//forward empty block to sparse
if( a == null ) {
executeSparse(null, b, scalars, c, n, rl, ru, rix);
return;
}
SideInput[] lb = createSparseSideInputs(b, true);
for( int i=rl; i<ru; i++ ) {
genexec(a.values(i), a.pos(i), lb, scalars,
c.values(i), c.pos(i), n, rix+i, i );
}
}
private void executeSparse(SparseBlock a, SideInput[] b, double[] scalars, DenseBlock c, int n, int rl, int ru, long rix) {
SideInput[] lb = createSparseSideInputs(b, true);
SparseRow empty = new SparseRowVector(1);
for( int i=rl; i<ru; i++ ) {
if( a!=null && !a.isEmpty(i) ) {
//call generated method
genexec(a.values(i), a.indexes(i), a.pos(i), lb, scalars,
c.values(i), c.pos(i), a.size(i), n, rix+i, i);
}
else
genexec(empty.values(), empty.indexes(), 0, lb, scalars,
c.values(i), c.pos(i), 0, n, rix+i, i);
}
}
//methods to be implemented by generated operators of type SpoofRowAggrgate
//local execution where grix==rix
protected final void genexec(double[] a, int ai,
SideInput[] b, double[] scalars, double[] c, int ci, int len, int rix) {
genexec(a, ai, b, scalars, c, ci, len, rix, rix);
}
protected final void genexec(double[] avals, int[] aix, int ai,
SideInput[] b, double[] scalars, double[] c, int ci, int alen, int n, int rix) {
genexec(avals, aix, ai, b, scalars, c, ci, alen, n, rix, rix);
}
//distributed execution with additional global row index
protected abstract void genexec(double[] a, int ai,
SideInput[] b, double[] scalars, double[] c, int ci, int len, long grix, int rix);
protected abstract void genexec(double[] avals, int[] aix, int ai,
SideInput[] b, double[] scalars, double[] c, int ci, int alen, int n, long grix, int rix);
/**
* Task for multi-threaded column aggregation operations.
*/
private class ParColAggTask implements Callable<DenseBlock>
{
private final MatrixBlock _a;
private final SideInput[] _b;
private final double[] _scalars;
private final int _clen, _clen2, _outLen;
private final int _rl, _ru;
protected ParColAggTask( MatrixBlock a, SideInput[] b, double[] scalars, int clen, int clen2, int outLen, int rl, int ru ) {
_a = a;
_b = b;
_scalars = scalars;
_clen = clen;
_clen2 = clen2;
_outLen = outLen;
_rl = rl;
_ru = ru;
}
@Override
public DenseBlock call() {
//allocate vector intermediates and partial output
if( _reqVectMem > 0 )
LibSpoofPrimitives.setupThreadLocalMemory(_reqVectMem, _clen, _clen2);
DenseBlock c = DenseBlockFactory.createDenseBlock(1, _outLen);
if( !_a.isInSparseFormat() )
executeDense(_a.getDenseBlock(), _b, _scalars, c, _clen, _rl, _ru, 0);
else
executeSparse(_a.getSparseBlock(), _b, _scalars, c, _clen, _rl, _ru, 0);
if( _reqVectMem > 0 )
LibSpoofPrimitives.cleanupThreadLocalMemory();
return c;
}
}
/**
* Task for multi-threaded execution with no or row aggregation.
*/
private class ParExecTask implements Callable<Long>
{
private final MatrixBlock _a;
private final SideInput[] _b;
private final MatrixBlock _c;
private final double[] _scalars;
private final int _clen;
private final int _clen2;
private final int _rl;
private final int _ru;
protected ParExecTask( MatrixBlock a, SideInput[] b, MatrixBlock c, double[] scalars, int clen, int clen2, int rl, int ru ) {
_a = a;
_b = b;
_c = c;
_scalars = scalars;
_clen = clen;
_clen2 = clen2;
_rl = rl;
_ru = ru;
}
@Override
public Long call() {
//allocate vector intermediates
if( _reqVectMem > 0 )
LibSpoofPrimitives.setupThreadLocalMemory(_reqVectMem, _clen, _clen2);
if( !_a.isInSparseFormat() )
executeDense(_a.getDenseBlock(), _b, _scalars, _c.getDenseBlock(), _clen, _rl, _ru, 0);
else
executeSparse(_a.getSparseBlock(), _b, _scalars, _c.getDenseBlock(), _clen, _rl, _ru, 0);
if( _reqVectMem > 0 )
LibSpoofPrimitives.cleanupThreadLocalMemory();
//maintain nnz for row partition
return _c.recomputeNonZeros(_rl, _ru-1, 0, _c.getNumColumns()-1);
}
}
}