Skip to content

Differential privacy for aggregates - add dp_laplace and dp_gaussian built-in functions with budget accounting - #2539

Open
andersonm-ibm wants to merge 30 commits into
apache:mainfrom
andersonm-ibm:diff_privacy_pub
Open

Differential privacy for aggregates - add dp_laplace and dp_gaussian built-in functions with budget accounting#2539
andersonm-ibm wants to merge 30 commits into
apache:mainfrom
andersonm-ibm:diff_privacy_pub

Conversation

@andersonm-ibm

@andersonm-ibm andersonm-ibm commented Jul 9, 2026

Copy link
Copy Markdown
  • Add two native DML built-ins for differentially private aggregate release "colMeans", "colSums" or "identity":
result = dp_laplace(X, "colMeans", sensitivity, epsilon)
result = dp_gaussian(X, "colSums", sensitivity, epsilon, delta)
  • Wire them through the full compilation pipeline:
    Builtins → BuiltinFunctionExpression → ParameterizedBuiltinOp HOP → ParameterizedBuiltin LOP → DPBuiltinCPInstruction.

  • Introduce DPBudgetAccountant, a session-scoped privacy budget tracker stored on ExecutionContext. Laplace releases use exact pure-ε composition; Gaussian releases use Rényi DP composition (Mironov 2017) with RDP → (ε,δ) conversion for tighter bounds. Raises DMLRuntimeException if cumulative spend exceeds the budget.

  • Unit tests covering constructor validation, Laplace/Gaussian composition, budget exhaustion for both mechanisms, mixed composition, release counting, and RDP mathematical invariants (sensitivity cancellation, ε-monotonicity).

  • End-to-end DML integration tests in DPBuiltinDMLTest verify noisy output differs from clean means by a statistically plausible amount.

  • Differential Privacy Benchmark:
    Four federated workers simulated on localhost, a logistic regression FedAvg loop in DML where the coordinator applies dp_gaussian to the aggregated gradient, a sweep over ε ∈ {0.5, 1, 4, 8} plus a non-private baseline, and a matplotlib accuracy-vs-ε plot saved as a PNG.

CC @ywcb00

@ywcb00 ywcb00 self-assigned this Jul 10, 2026
Maya Anderson added 6 commits July 15, 2026 00:13
…ransformation matrix T internally, returning T %*% X with noise fused into a single matrix multiply.
Lets a DML script declare its session-wide differential-privacy budget once at the top, instead of always falling back to the hardcoded default.
Resolved entirely at compile time: epsilon/delta must be literals, validated in BuiltinFunctionExpression and stored on DMLProgram during HOP construction, then read by ExecutionContext.getDPBudgetAccountant().
Four federated workers simulated on localhost, a logistic regression FedAvg loop in DML where the coordinator applies dp_gaussian to the aggregated gradient, a sweep over ε ∈ {0.5, 1, 4, 8} plus a non-private baseline, and a matplotlib accuracy-vs-ε plot saved as a PNG.

Add clip_norm (default 4.0) as a script parameter. Inside the private == 1 branch, each row's gradient contribution is clipped to L2-norm less than clip_norm.
@andersonm-ibm
andersonm-ibm marked this pull request as ready for review July 14, 2026 22:38
@andersonm-ibm andersonm-ibm changed the title WIP: Differential privacy for aggregates - add dp_laplace and dp_gaussian built-in functions with budget accounting Differential privacy for aggregates - add dp_laplace and dp_gaussian built-in functions with budget accounting Jul 14, 2026
@codecov

codecov Bot commented Jul 27, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 5.76923% with 245 lines in your changes missing coverage. Please review.
✅ Project coverage is 17.24%. Comparing base (e557836) to head (19f6fd0).
⚠️ Report is 27 commits behind head on main.

Files with missing lines Patch % Lines
...untime/instructions/cp/DPBuiltinCPInstruction.java 0.00% 94 Missing ⚠️
...apache/sysds/parser/BuiltinFunctionExpression.java 0.00% 52 Missing ⚠️
...e/sysds/runtime/privacy/dp/DPBudgetAccountant.java 0.00% 40 Missing ⚠️
...in/java/org/apache/sysds/parser/DMLTranslator.java 0.00% 29 Missing ⚠️
.../org/apache/sysds/hops/ParameterizedBuiltinOp.java 9.09% 7 Missing and 3 partials ⚠️
...va/org/apache/sysds/lops/ParameterizedBuiltin.java 0.00% 8 Missing ⚠️
.../main/java/org/apache/sysds/parser/DMLProgram.java 0.00% 6 Missing ⚠️
...ntime/controlprogram/context/ExecutionContext.java 16.66% 5 Missing ⚠️
...ysds/runtime/instructions/CPInstructionParser.java 0.00% 1 Missing ⚠️
Additional details and impacted files
@@              Coverage Diff              @@
##               main    #2539       +/-   ##
=============================================
- Coverage     71.61%   17.24%   -54.37%     
+ Complexity    49867    11450    -38417     
=============================================
  Files          1602     1625       +23     
  Lines        193054   194562     +1508     
  Branches      37792    38005      +213     
=============================================
- Hits         138247    33553   -104694     
- Misses        44031   155193   +111162     
+ Partials      10776     5816     -4960     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@ywcb00 ywcb00 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @andersonm-ibm,
I did a pass through the code and left some comments inline related to code style and integration in SystemDS.
The failing Java Test workflow was not caused by the changes of this PR.
All the best,
David

MMChain,
Union,
EINSUM,
DPBuiltin,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need a separate instruction type or could we also use the already existing ParameterizedBuiltin type?

Comment on lines +119 to +121
DP_LAPLACE("dp_laplace", false),
DP_GAUSSIAN("dp_gaussian", false),
DP_SET_BUDGET("dp_set_budget", false),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Keep the alphabetical order.

Comment on lines +2016 to +2018
String dpLaplaceQuery = getDPQueryLiteral(getSecondExpr());
long[] dpLaplaceDims = getDPOutputDims(dpLaplaceQuery,
getFirstExpr().getOutput().getDim1(), getFirstExpr().getOutput().getDim2());

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since these two functions are always called together, can we merge them to have a single function call to get the output dimensions?

case DECOMPRESS:
currBuiltinOp = new UnaryOp(target.getName(), target.getDataType(), ValueType.FP64, OpOp1.DECOMPRESS, expr);
break;
case DP_LAPLACE: {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Move these cases to the method processParameterizedBuiltinFunctionExpression above to simplify the parameter parsing.

* analysis that derives sensitivity from {@code T}'s column norms and a declared per-record bound on {@code X}; every
* other line in this class would stay unchanged.
*/
public class DPBuiltinCPInstruction extends ComputationCPInstruction {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we extend from ParameterizedBuiltinCPInstruction, since the DP builtins belong to the parameterized builtin functions?

private static final double EPS = 1e-9;

// =======================================================================
// 1. DPBudgetAccountant unit tests

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What does the '1.' refer to?

}

// =======================================================================
// 1b. DMLProgram / ExecutionContext.getDPBudgetAccountant() (dp_set_budget)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'1b.'?

*
* Full integration tests extend AutomatedTestBase and drive the DML runner.
* Each test:
* (a) Writes a DML script to a temp file.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Create the DML scripts directly as files under test/scripts, similar to other unit tests.

* Full integration tests extend AutomatedTestBase and drive the DML runner.
* Each test:
* (a) Writes a DML script to a temp file.
* (b) Provides input matrices via TestUtils.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use the method getRandomMatrix() from AutomatedTestBase for generating random test matrices.

Comment thread pom.xml
<artifactId>maven-surefire-plugin</artifactId>
<version>${maven-surefire-plugin.version}</version>
<configuration>
<reportFormat>plain</reportFormat>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the reason for this change? What is it needed for?

@github-project-automation github-project-automation Bot moved this from In Progress to In Review in SystemDS PR Queue Jul 27, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: In Review

Development

Successfully merging this pull request may close these issues.

2 participants