Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
55 commits
Select commit Hold shift + click to select a range
372afa0
Initial DPBuiltinCPInstruction and RDPAccountant
Jun 26, 2026
8b048ef
Add dp_gaussian and dp_laplace operations
Jun 26, 2026
5a09e64
Fix test package name.
Jul 2, 2026
2eac049
Fix Laplace accountant
Jul 6, 2026
2aa916c
Add unit tests
Jul 6, 2026
232c1fc
Rename RDPAccountant to DPBudgetAccountant
Jul 6, 2026
6359c2e
Delete RDPAccountant
Jul 6, 2026
dbee4b2
Fix parseInstruction() comment
Jul 6, 2026
5ddaf1b
Integration tests
Jul 6, 2026
d1d4f46
Update test comments
Jul 6, 2026
310e0d0
Fix integration test to compare clean to noisy data
Jul 7, 2026
7a226d5
Make integration test more readable
Jul 7, 2026
eedd7d2
Comment fix
Jul 7, 2026
64adfbe
Fix L1 L2 sensitivity parameter documentation
Jul 7, 2026
fa9b0f4
junit output format
Jul 7, 2026
74a53da
dp_laplace and dp_gaussian now take the original matrix and build a t…
Jul 9, 2026
29f865f
Remove 4th and 5th parameters and instead get them in a loop, accordi…
Jul 13, 2026
e3ae8dc
Add dp_set_budget(epsilon, delta) built-in
Jul 14, 2026
9c79910
Change DPBudgetAccountant defaults to be constants
Jul 14, 2026
9b27a8e
Cleanup comments
Jul 14, 2026
337c82f
Differential Privacy Benchmark
Jul 14, 2026
c9dff61
Checkstyle fix
Jul 21, 2026
76189c9
Add missing licenses
Jul 21, 2026
c2ef846
More checkstyle fixes
Jul 22, 2026
877d80d
Add missing break. Fixes test
Jul 22, 2026
5b3eb3d
Change ParameterizedBuiltinOp.compare() so that two syntactically ide…
Jul 28, 2026
d2e9981
Address David's review comments: unite utility methods, reorder alpha…
Jul 28, 2026
3cfbd82
Route dp_laplace/dp_gaussian through the named-parameter builtin path
Jul 28, 2026
598c2e4
Code review comments: remove special characters
Jul 29, 2026
9532e4d
Unite tests
Jul 31, 2026
4270c4d
Remove unneeded pom.xml change
Jul 31, 2026
09cb806
Use getRandomMatrix in DPBuiltinDMLTest and call it in setup instead …
Aug 1, 2026
089a405
Removed python requirements file
Aug 1, 2026
d8a28f7
Reorder Gauss and Laplace cases alphabetically
Aug 1, 2026
a485f49
Add parsing tests
Aug 2, 2026
528ff9c
Use computeGaussianSigma
Aug 2, 2026
e4ead7d
Change gaussian noise generation to use MatrixBlock randOperation, an…
Aug 2, 2026
c58e5fa
Checkstyle
Aug 2, 2026
8940c8d
use an existing ParameterizedBuiltin type, instead of a new instructi…
Aug 3, 2026
4f6335e
Comment readability
Aug 4, 2026
7716e32
Return DPBuiltinCPInstruction, but now it extends ParameterizedBuilti…
Aug 4, 2026
7c32deb
Comment cleanups after review fixes
Aug 5, 2026
c57c177
Move DP benchmark under dp folder and add a README
Aug 5, 2026
afee186
Add license to requirements.txt
Aug 9, 2026
e9c9d8c
Changes according to format-changed.sh
Aug 25, 2026
9001036
Fix doc reference, improve readability
Sep 1, 2026
c007b50
chore(**): apply formatting
ywcb00 Sep 8, 2026
70be254
fix(benchmark/dp/scripts/**): remove the benchmark utilities script w…
ywcb00 Sep 18, 2026
586a6cf
refactor(scripts/staging/dp_benchmark/**): move the differential priv…
ywcb00 Sep 18, 2026
0171f7a
feat(scripts/staging/dp_benchmark/**): remove differential privacy be…
ywcb00 Sep 18, 2026
7428567
refactor(**): perform some formatting
ywcb00 Sep 18, 2026
8d74228
fix(test/component/cp/DPBuiltinOpsTest.java): remove too fine-grained…
ywcb00 Sep 18, 2026
cfee3d4
chore(test/functions/privacy/dp/DPBuiltinDMLTest.java): remove some t…
ywcb00 Sep 20, 2026
aad2cd1
feat(.github/workflows/javaTests.yml): add privacy function tests to …
ywcb00 Sep 20, 2026
5e67549
chore(main/runtime/instructions/cp/DPBuiltinCPInstruction.java): log …
ywcb00 Sep 20, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/javaTests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ jobs:
"**.functions.frame.**,**.functions.indexing.**,**.functions.io.**,**.functions.iogen.**",
"**.functions.dnn.**,**.functions.ooc.**",
"**.functions.paramserv.**",
"**.functions.privacy.**",
"**.functions.recompile.**,**.functions.misc.**",
"**.functions.mlcontext.**",
"**.functions.nary.**,**.functions.quaternary.**",
Expand Down
3 changes: 3 additions & 0 deletions src/main/java/org/apache/sysds/common/Builtins.java
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,7 @@ public enum Builtins {
SETDIFF("setdiff", true),
DIST("dist", true),
DMV("dmv", true),
DP_SET_BUDGET("dp_set_budget", false),
DROP_INVALID_TYPE("dropInvalidType", false),
DROP_INVALID_LENGTH("dropInvalidLength", false),
EIGEN("eigen", false, ReturnType.MULTI_RETURN),
Expand Down Expand Up @@ -377,6 +378,8 @@ public enum Builtins {
COUNT_DISTINCT_APPROX_ROW("rowCountDistinctApprox", false, true),
COUNT_DISTINCT_APPROX_COL("colCountDistinctApprox", false, true),
CVLM("cvlm", true, false),
DP_GAUSSIAN("dp_gaussian", false, true),
DP_LAPLACE("dp_laplace", false, true),
GROUPEDAGG("aggregate", "groupedAggregate", false, true),
INVCDF("icdf", false, true),
LISTNV("list", false, true), //note: builtin and parbuiltin
Expand Down
18 changes: 10 additions & 8 deletions src/main/java/org/apache/sysds/common/Opcodes.java
Original file line number Diff line number Diff line change
Expand Up @@ -196,24 +196,26 @@ public enum Opcodes {

//Parametrized builtin functions
AUTODIFF("autoDiff", InstructionType.ParameterizedBuiltin),
CONTAINS("contains", InstructionType.ParameterizedBuiltin),
PARAMSERV("paramserv", InstructionType.ParameterizedBuiltin),
NVLIST("nvlist", InstructionType.ParameterizedBuiltin),
CDF("cdf", InstructionType.ParameterizedBuiltin),
INVCDF("invcdf", InstructionType.ParameterizedBuiltin),
CONTAINS("contains", InstructionType.ParameterizedBuiltin),
DP_GAUSSIAN("dp_gaussian", InstructionType.ParameterizedBuiltin),
DP_LAPLACE("dp_laplace", InstructionType.ParameterizedBuiltin),
GROUPEDAGG("groupedagg", InstructionType.ParameterizedBuiltin),
RMEMPTY("rmempty", InstructionType.ParameterizedBuiltin),
REPLACE("replace", InstructionType.ParameterizedBuiltin),
INVCDF("invcdf", InstructionType.ParameterizedBuiltin),
LOWERTRI("lowertri", InstructionType.ParameterizedBuiltin),
UPPERTRI("uppertri", InstructionType.ParameterizedBuiltin),
NVLIST("nvlist", InstructionType.ParameterizedBuiltin),
PARAMSERV("paramserv", InstructionType.ParameterizedBuiltin),
REPLACE("replace", InstructionType.ParameterizedBuiltin),
REXPAND("rexpand", InstructionType.ParameterizedBuiltin),
TOSTRING("toString", InstructionType.ParameterizedBuiltin),
RMEMPTY("rmempty", InstructionType.ParameterizedBuiltin),
TOKENIZE("tokenize", InstructionType.ParameterizedBuiltin),
TOSTRING("toString", InstructionType.ParameterizedBuiltin),
TRANSFORMAPPLY("transformapply", InstructionType.ParameterizedBuiltin),
TRANSFORMDECODE("transformdecode", InstructionType.ParameterizedBuiltin),
TRANSFORMCOLMAP("transformcolmap", InstructionType.ParameterizedBuiltin),
TRANSFORMMETA("transformmeta", InstructionType.ParameterizedBuiltin),
TRANSFORMENCODE("transformencode", InstructionType.MultiReturnParameterizedBuiltin, InstructionType.MultiReturnBuiltin),
UPPERTRI("uppertri", InstructionType.ParameterizedBuiltin),

GET_CATEGORICAL_MASK("get_categorical_mask", InstructionType.Binary),

Expand Down
7 changes: 3 additions & 4 deletions src/main/java/org/apache/sysds/common/Types.java
Original file line number Diff line number Diff line change
Expand Up @@ -806,10 +806,9 @@ public static ReOrgOp valueOfByOpcode(String opcode) {

/** Parameterized operations that require named variable arguments */
public enum ParamBuiltinOp {
AUTODIFF, CDF, CONTAINS, INVALID, INVCDF, GROUPEDAGG, RMEMPTY, REPLACE, REXPAND,
LOWER_TRI, UPPER_TRI,
TRANSFORMAPPLY, TRANSFORMDECODE, TRANSFORMCOLMAP, TRANSFORMMETA,
TOKENIZE, TOSTRING, LIST, PARAMSERV
AUTODIFF, CDF, CONTAINS, DP_GAUSSIAN, DP_LAPLACE, INVALID, INVCDF, GROUPEDAGG, RMEMPTY, REPLACE, REXPAND,
LOWER_TRI, UPPER_TRI, TRANSFORMAPPLY, TRANSFORMDECODE, TRANSFORMCOLMAP, TRANSFORMMETA, TOKENIZE, TOSTRING, LIST,
PARAMSERV
}

/** Deep Neural Network specific operations */
Expand Down
52 changes: 33 additions & 19 deletions src/main/java/org/apache/sysds/hops/ParameterizedBuiltinOp.java
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,7 @@ public Lop constructLops()
}
case CONTAINS:
case CDF:
case INVCDF:
case INVCDF:
case REPLACE:
case LOWER_TRI:
case UPPER_TRI:
Expand All @@ -194,10 +194,12 @@ public Lop constructLops()
case TOSTRING:
case PARAMSERV:
case LIST:
case AUTODIFF:{
case AUTODIFF:
case DP_GAUSSIAN:
case DP_LAPLACE: {
ParameterizedBuiltin pbilop = new ParameterizedBuiltin(
inputlops, _op, getDataType(), getValueType(), et);
if( isMultiThreadedOpType() )
if(isMultiThreadedOpType())
pbilop.setNumThreads(OptimizerUtils.getConstrainedNumThreads(_maxNumThreads));
setOutputDimensions(pbilop);
setLineNumbers(pbilop);
Expand Down Expand Up @@ -688,7 +690,17 @@ else if( _op == ParamBuiltinOp.TRANSFORMAPPLY ) {
return new MatrixCharacteristics(dc.getRows(), dc.getCols(), -1, dc.getLength());
}
}

else if(_op == ParamBuiltinOp.DP_GAUSSIAN || _op == ParamBuiltinOp.DP_LAPLACE) {
if(dc.dimsKnown()) {
Hop query = getParameterHop("query");
String queryVal = (query instanceof LiteralOp) ? ((LiteralOp) query).getStringValue() : null;
if("colMeans".equals(queryVal) || "colSums".equals(queryVal))
ret = new MatrixCharacteristics(1, dc.getCols(), -1, dc.getCols());
else if("identity".equals(queryVal))
ret = new MatrixCharacteristics(dc.getRows(), dc.getCols(), -1, dc.getLength());
}
}

return ret;
}
@Override
Expand Down Expand Up @@ -755,10 +767,10 @@ && getTargetHop().areDimsBelowThreshold() ) {
// 2. For paramserv function, always be CP mode so that
// the parameter server could have a central instruction
// to determine the local or remote workers
if (_op == ParamBuiltinOp.TRANSFORMCOLMAP || _op == ParamBuiltinOp.TRANSFORMMETA
|| _op == ParamBuiltinOp.TOSTRING || _op == ParamBuiltinOp.LIST
|| _op == ParamBuiltinOp.CDF || _op == ParamBuiltinOp.INVCDF
|| _op == ParamBuiltinOp.PARAMSERV) {
if(_op == ParamBuiltinOp.TRANSFORMCOLMAP || _op == ParamBuiltinOp.TRANSFORMMETA ||
_op == ParamBuiltinOp.TOSTRING || _op == ParamBuiltinOp.LIST || _op == ParamBuiltinOp.CDF ||
_op == ParamBuiltinOp.INVCDF || _op == ParamBuiltinOp.PARAMSERV || _op == ParamBuiltinOp.DP_GAUSSIAN ||
_op == ParamBuiltinOp.DP_LAPLACE) {
_etype = ExecType.CP;
}

Expand Down Expand Up @@ -940,17 +952,19 @@ public boolean compare( Hop that )
{
if( !(that instanceof ParameterizedBuiltinOp) )
return false;

ParameterizedBuiltinOp that2 = (ParameterizedBuiltinOp)that;
boolean ret = (_op == that2._op
&& _paramIndexMap!=null && that2._paramIndexMap!=null
&& _paramIndexMap.size() == that2._paramIndexMap.size()
&& _outputEmptyBlocks == that2._outputEmptyBlocks
&& _outputPermutationMatrix == that2._outputPermutationMatrix );
if( ret )
{
for( Entry<String,Integer> e : _paramIndexMap.entrySet() )
{

// NOTE: dp_gaussian, dp_laplace draw fresh random noise on every call and record a
// privacy-budget charge as a side effect (see DPBuiltinOps.release), so two
// syntactically identical calls must never be merged into one execution.
if(_op == ParamBuiltinOp.DP_GAUSSIAN || _op == ParamBuiltinOp.DP_LAPLACE)
return false;

ParameterizedBuiltinOp that2 = (ParameterizedBuiltinOp) that;
boolean ret = (_op == that2._op && _paramIndexMap != null && that2._paramIndexMap != null &&
_paramIndexMap.size() == that2._paramIndexMap.size() && _outputEmptyBlocks == that2._outputEmptyBlocks &&
_outputPermutationMatrix == that2._outputPermutationMatrix);
if(ret) {
for(Entry<String, Integer> e : _paramIndexMap.entrySet()) {
String key1 = e.getKey();
int pos1 = e.getValue();
int pos2 = that2._paramIndexMap.get(key1);
Expand Down
14 changes: 13 additions & 1 deletion src/main/java/org/apache/sysds/lops/ParameterizedBuiltin.java
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,19 @@ public String getInstructions(String output)
compileGenericParamMap(sb, _inputParams);
break;
}

case DP_GAUSSIAN: {
sb.append(Opcodes.DP_GAUSSIAN);
sb.append(OPERAND_DELIMITOR);
compileGenericParamMap(sb, _inputParams);
break;
}
case DP_LAPLACE: {
sb.append(Opcodes.DP_LAPLACE);
sb.append(OPERAND_DELIMITOR);
compileGenericParamMap(sb, _inputParams);
break;
}

default:
throw new LopsException(this.printErrorLocation() + "In ParameterizedBuiltin Lop, Unknown operation: " + _operation);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1995,16 +1995,40 @@ else if(this.getOpCode() == Builtins.MAX_POOL || this.getOpCode() == Builtins.AV
}
break;
case LOCAL:
if(OptimizerUtils.ALLOW_SCRIPT_LEVEL_LOCAL_COMMAND){
if(OptimizerUtils.ALLOW_SCRIPT_LEVEL_LOCAL_COMMAND) {
checkNumParameters(1);
checkMatrixParam(getFirstExpr());
output.setDataType(DataType.MATRIX);
output.setDimensions(id.getDim1(), id.getDim2());
output.setBlocksize (id.getBlocksize());
output.setValueType(id.getValueType());
}
else
else {
raiseValidateError("Local instruction not allowed in dml script");
}
break;
case DP_SET_BUDGET: {
checkNumParameters(2);
checkScalarParam(getFirstExpr());
checkScalarParam(getSecondExpr());
// resolved entirely at compile time (see DMLTranslator), so both
// arguments must be known before the HOP DAG is built.
if (!isConstant(getFirstExpr()) || !isConstant(getSecondExpr()))
raiseValidateError(getOpCode() + ": 'epsilon' and 'delta' must be compile-time numeric literals",
false, LanguageErrorCodes.INVALID_PARAMETERS);
double dpSetBudgetEpsilon = getDoubleValue(getFirstExpr());
double dpSetBudgetDelta = getDoubleValue(getSecondExpr());
if (dpSetBudgetEpsilon <= 0)
raiseValidateError(getOpCode() + ": epsilon must be > 0, got " + dpSetBudgetEpsilon,
false, LanguageErrorCodes.INVALID_PARAMETERS);
if ((dpSetBudgetDelta <= 0) || (dpSetBudgetDelta >= 1))
raiseValidateError(getOpCode() + ": delta must be in (0,1), got " + dpSetBudgetDelta,
false, LanguageErrorCodes.INVALID_PARAMETERS);
output.setDataType(DataType.SCALAR);
output.setValueType(ValueType.FP64);
output.setDimensions(0, 0);
break;
}
case COMPRESS:
case DECOMPRESS:
if(OptimizerUtils.ALLOW_SCRIPT_LEVEL_COMPRESS_COMMAND){
Expand Down
25 changes: 24 additions & 1 deletion src/main/java/org/apache/sysds/parser/DMLProgram.java
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,13 @@ public class DMLProgram
private ArrayList<StatementBlock> _blocks;
private Map<String, FunctionDictionary<FunctionStatementBlock>> _namespaces;
private boolean _containsRemoteParfor;


/**
* Session-wide differential privacy budget resolved at compile time during HOP construction.
*/
private Double _dpBudgetEpsilon;
private Double _dpBudgetDelta;

public DMLProgram(){
_blocks = new ArrayList<>();
_namespaces = new HashMap<>();
Expand Down Expand Up @@ -67,6 +73,23 @@ public void setContainsRemoteParfor(boolean flag) {
public boolean containsRemoteParfor() {
return _containsRemoteParfor;
}

public void setDPBudget(double epsilon, double delta) {
_dpBudgetEpsilon = epsilon;
_dpBudgetDelta = delta;
}

public boolean hasDPBudget() {
return _dpBudgetEpsilon != null;
}

public double getDPBudgetEpsilon() {
return _dpBudgetEpsilon;
}

public double getDPBudgetDelta() {
return _dpBudgetDelta;
}

public static boolean isInternalNamespace(String namespace) {
return DEFAULT_NAMESPACE.equals(namespace)
Expand Down
15 changes: 15 additions & 0 deletions src/main/java/org/apache/sysds/parser/DMLTranslator.java
Original file line number Diff line number Diff line change
Expand Up @@ -2015,6 +2015,8 @@ private Hop processParameterizedBuiltinFunctionExpression(ParameterizedBuiltinFu
case TRANSFORMMETA:
case PARAMSERV:
case AUTODIFF:
case DP_GAUSSIAN:
case DP_LAPLACE:
currBuiltinOp = new ParameterizedBuiltinOp(target.getName(), target.getDataType(),
target.getValueType(), ParamBuiltinOp.valueOf(source.getOpCode().name()), paramHops);
break;
Expand Down Expand Up @@ -2590,6 +2592,19 @@ else if ( sop.equalsIgnoreCase(Opcodes.NOTEQUAL.toString()) )
case DECOMPRESS:
currBuiltinOp = new UnaryOp(target.getName(), target.getDataType(), ValueType.FP64, OpOp1.DECOMPRESS, expr);
break;
case DP_SET_BUDGET:
// Resolved entirely at compile time; the budget is applied directly to the DMLProgram (reachable later from
// ExecutionContext via Program.getDMLProg())
if (_dmlProg.hasDPBudget())
throw new LanguageException(source.getOpCode() + ": dp_set_budget may only be called once per "
+ "script (already set to epsilon=" + _dmlProg.getDPBudgetEpsilon()
+ ", delta=" + _dmlProg.getDPBudgetDelta() + ")");
if (!(expr instanceof LiteralOp) || !(expr2 instanceof LiteralOp))
throw new LanguageException(source.getOpCode()
+ ": epsilon and delta must be compile-time numeric literals");
_dmlProg.setDPBudget(((LiteralOp) expr).getDoubleValue(), ((LiteralOp) expr2).getDoubleValue());
currBuiltinOp = expr; // echo epsilon back as confirmation
break;
case QUANTIZE_COMPRESS:
currBuiltinOp = new BinaryOp(target.getName(), target.getDataType(), target.getValueType(), OpOp2.valueOf(source.getOpCode().name()), expr, expr2);
break;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,11 @@ public void validateExpression(HashMap<String, DataIdentifier> ids, HashMap<Stri
validateUnique(output, conditional);
break;

case DP_GAUSSIAN:
case DP_LAPLACE:
validateDpMechanism(output, conditional);
break;

default: //always unconditional (because unsupported operation)
//handle common issue of transformencode
if( getOpCode()==Builtins.TRANSFORMENCODE )
Expand Down Expand Up @@ -770,6 +775,54 @@ private void validateReplace(DataIdentifier output, boolean conditional) {
output.setDimensions(target.getOutput().getDim1(), target.getOutput().getDim2());
}

private void validateDpMechanism(DataIdentifier output, boolean conditional) {
// support X as the sole positional (unnamed) parameter, like lowerTri(X, ...)
HashMap<String, Expression> varParams = getVarParams();
if(varParams.containsKey(null))
varParams.put("target", varParams.remove(null));

boolean gaussian = getOpCode() == Builtins.DP_GAUSSIAN;
Set<String> valid = gaussian ?
CollectionUtils.asSet("target", "query", "sensitivity", "epsilon", "delta") :
CollectionUtils.asSet("target", "query", "sensitivity", "epsilon");
checkInvalidParameters(getOpCode(), varParams, valid);

Expression target = getVarParam("target");
checkTargetParam(target, conditional);
for(String param : valid)
if(!param.equals("target"))
checkScalarParam(getOpCode().getName(), param, conditional);

long[] dims = getDPOutputDims(getVarParam("query"), target.getOutput().getDim1(), target.getOutput().getDim2());
output.setDataType(DataType.MATRIX);
output.setValueType(ValueType.FP64);
output.setDimensions(dims[0], dims[1]);
}

/** Output dimensions of T %*% X for the given named query, X being n x d. */
private long[] getDPOutputDims(Expression queryExpr, long n, long d) {
// dp_gaussian/dp_laplace require the "query" parameter to be a compile-time string literal so that the output
// shape
// (and thus the transformation matrix T built at runtime) is known during validation.
if(!(queryExpr instanceof StringIdentifier))
raiseValidateError(getOpCode() + ": 'query' must be a string literal", false,
LanguageErrorCodes.INVALID_PARAMETERS);
String query = ((StringIdentifier) queryExpr).getValue();

switch(query) {
case "colMeans":
case "colSums":
return new long[] {1, d};
case "identity":
return new long[] {n, d};
default:
raiseValidateError(
getOpCode() + ": unknown query type '" + query + "' (expected colMeans, colSums, or identity)",
false, LanguageErrorCodes.INVALID_PARAMETERS);
return null; // unreachable
}
}

private void checkScalarParam(String group, String param, boolean conditional) {
Expression eparam = getVarParam(param);
if( eparam==null ) {
Expand Down
Loading
Loading