Skip to content
This repository was archived by the owner on Jun 20, 2025. It is now read-only.

Commit 692e27e

Browse files
Chenyu Liufacebook-github-bot
authored andcommitted
Add ComputeAttributionHelper function for new output format (#1308)
Summary: Pull Request resolved: #1308 # Reformat Attribution Output We will apply performance improvements to private attribution product (game) by changing the format of attribution result. For this we will need to make changes to both private attribution and private aggregation stages. The original format of attribution result is: { "last_click_1d": { "default": { "0": [ { "is_attributed": true }, { "is_attributed": false }, { "is_attributed": false }, { "is_attributed": false }, { "is_attributed": false } ] } } } Proposed format: [ {ad_id, conversion_value, is_attributed}, {ad_id, conversion_value, is_attributed}, {ad_id, conversion_value, is_attributed}, {ad_id, conversion_value, is_attributed}, ] The design plan: https://docs.google.com/document/d/1QyBtCkTeZA8IXAkok0n8EhfCZeLTU0SSN1VL57vjBCo/edit?usp=sharing # This Diff Add ComputAttributionHelperV2 function for new output format. # This Stack 1. Add a flag to validate whether to use new vs old output format in Private Attribution. 2. Modify PCS stage for attribution with the new flag. 3. Parse the input for new output format. 4. Add a new output format file in attribution game. 5. **Add ComputAttributionHelperV2 function for new output format.** 6. Update computeAttributions function. 7. Update unit tests for PCF2 Attribution logic. 8. Add json test files for new output format. 9. Modify revealXORedResult method for new output. 10. Add unit tests for new output format - testCorrectness. Reviewed By: chualynn Differential Revision: D37765615 fbshipit-source-id: 858302db5de13708daccb28a17591388d4b1ff88
1 parent 31914fe commit 692e27e

File tree

3 files changed

+201
-105
lines changed

3 files changed

+201
-105
lines changed

fbpcs/emp_games/pcf2_attribution/AttributionGame.h

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,20 @@ class AttributionGame : public fbpcf::frontend::MpcGame<schedulerId> {
9090
const std::vector<std::vector<SecTimestamp<schedulerId, usingBatch>>>&
9191
thresholds,
9292
size_t batchSize);
93+
94+
const std::vector<AttributionReformattedOutputFmt<schedulerId, usingBatch>>
95+
computeAttributionsHelperV2(
96+
const std::vector<
97+
PrivateTouchpoint<schedulerId, usingBatch, inputEncryption>>&
98+
touchpoints,
99+
const std::vector<
100+
PrivateConversion<schedulerId, usingBatch, inputEncryption>>&
101+
conversions,
102+
const AttributionRule<schedulerId, usingBatch, inputEncryption>&
103+
attributionRule,
104+
const std::vector<std::vector<SecTimestamp<schedulerId, usingBatch>>>&
105+
thresholds,
106+
size_t batchSize);
93107
};
94108

95109
} // namespace pcf2_attribution

fbpcs/emp_games/pcf2_attribution/AttributionGame_impl.h

Lines changed: 187 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -209,88 +209,212 @@ AttributionGame<schedulerId, usingBatch, inputEncryption>::
209209
// know that it is the preferred touchpoint as well.
210210
// Thus at the end we will get the fully reversed attribution match vector of
211211
// conversions and touchpoints.
212-
if (FLAGS_use_new_output_format) {
213-
// ToDo: Implement logic for generating attribution output in new format.
214-
} else {
215-
for (auto conversion = conversions.rbegin();
216-
conversion != conversions.rend();
217-
++conversion) {
218-
auto conv = *conversion;
212+
for (auto conversion = conversions.rbegin(); conversion != conversions.rend();
213+
++conversion) {
214+
auto conv = *conversion;
215+
216+
if constexpr (usingBatch) {
217+
OMNISCIENT_ONLY_XLOGF(
218+
DBG,
219+
"Computing attributions for conversions: {}",
220+
common::vecToString(
221+
conv.ts.openToParty(common::PUBLISHER).getValue()));
222+
} else {
223+
OMNISCIENT_ONLY_XLOGF(
224+
DBG,
225+
"Computing attributions for conversion: {}",
226+
conv.ts.openToParty(common::PUBLISHER).getValue());
227+
}
228+
229+
// store if conversion has already been attributed
230+
SecBit<schedulerId, usingBatch> hasAttributedTouchpoint;
231+
if constexpr (usingBatch) {
232+
hasAttributedTouchpoint = SecBit<schedulerId, usingBatch>{
233+
std::vector<bool>(batchSize, false), common::PUBLISHER};
234+
} else {
235+
hasAttributedTouchpoint =
236+
SecBit<schedulerId, usingBatch>{false, common::PUBLISHER};
237+
}
238+
239+
CHECK_EQ(touchpoints.size(), thresholds.size())
240+
<< "touchpoints and thresholds are not the same length.";
241+
242+
for (size_t i = touchpoints.size(); i >= 1; --i) {
243+
auto tp = touchpoints.at(i - 1);
244+
auto threshold = thresholds.at(i - 1);
219245

220246
if constexpr (usingBatch) {
221247
OMNISCIENT_ONLY_XLOGF(
222248
DBG,
223-
"Computing attributions for conversions: {}",
249+
"Checking touchpoints: {}",
224250
common::vecToString(
225-
conv.ts.openToParty(common::PUBLISHER).getValue()));
251+
tp.ts.openToParty(common::PUBLISHER).getValue()));
226252
} else {
227253
OMNISCIENT_ONLY_XLOGF(
228254
DBG,
229-
"Computing attributions for conversion: {}",
230-
conv.ts.openToParty(common::PUBLISHER).getValue());
255+
"Checking touchpoint: {}",
256+
tp.ts.openToParty(common::PUBLISHER).getValue());
231257
}
232258

233-
// store if conversion has already been attributed
234-
SecBit<schedulerId, usingBatch> hasAttributedTouchpoint;
259+
auto isTouchpointAttributable =
260+
attributionRule.isAttributable(tp, conv, threshold);
261+
262+
auto isAttributed = isTouchpointAttributable & !hasAttributedTouchpoint;
263+
264+
hasAttributedTouchpoint = isAttributed | hasAttributedTouchpoint;
265+
235266
if constexpr (usingBatch) {
236-
hasAttributedTouchpoint = SecBit<schedulerId, usingBatch>{
237-
std::vector<bool>(batchSize, false), common::PUBLISHER};
267+
OMNISCIENT_ONLY_XLOGF(
268+
DBG,
269+
"isTouchpointAttributable={}, isAttributed={}, hasAttributedTouchpoint={}",
270+
common::vecToString(
271+
isTouchpointAttributable.extractBit().getValue()),
272+
common::vecToString(isAttributed.extractBit().getValue()),
273+
common::vecToString(
274+
hasAttributedTouchpoint.extractBit().getValue()));
238275
} else {
239-
hasAttributedTouchpoint =
240-
SecBit<schedulerId, usingBatch>{false, common::PUBLISHER};
276+
OMNISCIENT_ONLY_XLOGF(
277+
DBG,
278+
"isTouchpointAttributable={}, isAttributed={}, hasAttributedTouchpoint={}",
279+
isTouchpointAttributable.extractBit().getValue(),
280+
isAttributed.extractBit().getValue(),
281+
hasAttributedTouchpoint.extractBit().getValue());
241282
}
242283

243-
CHECK_EQ(touchpoints.size(), thresholds.size())
244-
<< "touchpoints and thresholds are not the same length.";
245-
246-
for (size_t i = touchpoints.size(); i >= 1; --i) {
247-
auto tp = touchpoints.at(i - 1);
248-
auto threshold = thresholds.at(i - 1);
249-
250-
if constexpr (usingBatch) {
251-
OMNISCIENT_ONLY_XLOGF(
252-
DBG,
253-
"Checking touchpoints: {}",
254-
common::vecToString(
255-
tp.ts.openToParty(common::PUBLISHER).getValue()));
256-
} else {
257-
OMNISCIENT_ONLY_XLOGF(
258-
DBG,
259-
"Checking touchpoint: {}",
260-
tp.ts.openToParty(common::PUBLISHER).getValue());
261-
}
284+
attributions.push_back(isAttributed);
285+
}
286+
}
287+
std::reverse(attributions.begin(), attributions.end());
288+
return attributions;
289+
}
262290

263-
auto isTouchpointAttributable =
264-
attributionRule.isAttributable(tp, conv, threshold);
265-
266-
auto isAttributed = isTouchpointAttributable & !hasAttributedTouchpoint;
267-
268-
hasAttributedTouchpoint = isAttributed | hasAttributedTouchpoint;
269-
270-
if constexpr (usingBatch) {
271-
OMNISCIENT_ONLY_XLOGF(
272-
DBG,
273-
"isTouchpointAttributable={}, isAttributed={}, hasAttributedTouchpoint={}",
274-
common::vecToString(
275-
isTouchpointAttributable.extractBit().getValue()),
276-
common::vecToString(isAttributed.extractBit().getValue()),
277-
common::vecToString(
278-
hasAttributedTouchpoint.extractBit().getValue()));
279-
} else {
280-
OMNISCIENT_ONLY_XLOGF(
281-
DBG,
282-
"isTouchpointAttributable={}, isAttributed={}, hasAttributedTouchpoint={}",
283-
isTouchpointAttributable.extractBit().getValue(),
284-
isAttributed.extractBit().getValue(),
285-
hasAttributedTouchpoint.extractBit().getValue());
286-
}
291+
template <
292+
int schedulerId,
293+
bool usingBatch,
294+
common::InputEncryption inputEncryption>
295+
const std::vector<AttributionReformattedOutputFmt<schedulerId, usingBatch>>
296+
AttributionGame<schedulerId, usingBatch, inputEncryption>::
297+
computeAttributionsHelperV2(
298+
const std::vector<
299+
PrivateTouchpoint<schedulerId, usingBatch, inputEncryption>>&
300+
touchpoints,
301+
const std::vector<
302+
PrivateConversion<schedulerId, usingBatch, inputEncryption>>&
303+
conversions,
304+
const AttributionRule<schedulerId, usingBatch, inputEncryption>&
305+
attributionRule,
306+
const std::vector<std::vector<SecTimestamp<schedulerId, usingBatch>>>&
307+
thresholds,
308+
size_t batchSize) {
309+
if constexpr (usingBatch) {
310+
if (batchSize == 0) {
311+
throw std::invalid_argument(
312+
"Must provide positive batch size for batch execution!");
313+
}
314+
}
315+
std::vector<AttributionReformattedOutputFmt<schedulerId, usingBatch>>
316+
attributionsOutput;
317+
// We will be attributing on a sorted vector of touchpoints and conversions
318+
// (based on timestamps).
319+
// The preferred touchpoint for a conversion will be a valid attributable
320+
// touchpoint with nearest timestamp to the conversion. In order to compute
321+
// this efficiently, we will traverse backwards on both conversion and
322+
// touchpoint vector. So that when we find a valid attributable touchpoint, we
323+
// know that it is the preferred touchpoint as well.
324+
// Thus at the end we will get the fully reversed attribution match vector of
325+
// conversions and touchpoints.
326+
for (auto conversion = conversions.rbegin(); conversion != conversions.rend();
327+
++conversion) {
328+
auto conv = *conversion;
329+
330+
if constexpr (usingBatch) {
331+
OMNISCIENT_ONLY_XLOGF(
332+
DBG,
333+
"Computing attributions for conversions: {}",
334+
common::vecToString(
335+
conv.ts.openToParty(common::PUBLISHER).getValue()));
336+
} else {
337+
OMNISCIENT_ONLY_XLOGF(
338+
DBG,
339+
"Computing attribution for conversion: {}",
340+
conv.ts.openToParty(common::PUBLISHER).getValue());
341+
}
342+
343+
// store if conversion has already been attributed
344+
SecBit<schedulerId, usingBatch> hasAttributedTouchpoint;
345+
if constexpr (usingBatch) {
346+
hasAttributedTouchpoint = SecBit<schedulerId, usingBatch>{
347+
std::vector<bool>(batchSize, false), common::PUBLISHER};
348+
} else {
349+
hasAttributedTouchpoint =
350+
SecBit<schedulerId, usingBatch>{false, common::PUBLISHER};
351+
}
352+
353+
CHECK_EQ(touchpoints.size(), thresholds.size())
354+
<< "touchpoints and thresholds are not the same length.";
355+
356+
SecAdId<schedulerId, usingBatch> attributedAdId;
357+
uint64_t defaultAdId = 0;
358+
if constexpr (usingBatch) {
359+
// initialize the ad_id to be 0, is_attributed to be false:
360+
attributedAdId = SecAdId<schedulerId, usingBatch>{
361+
std::vector<uint64_t>(batchSize, defaultAdId), common::PUBLISHER};
362+
} else {
363+
attributedAdId =
364+
SecAdId<schedulerId, usingBatch>(defaultAdId, common::PUBLISHER);
365+
}
366+
for (size_t i = touchpoints.size(); i >= 1; --i) {
367+
auto tp = touchpoints.at(i - 1);
368+
auto threshold = thresholds.at(i - 1);
287369

288-
attributions.push_back(isAttributed);
370+
if constexpr (usingBatch) {
371+
OMNISCIENT_ONLY_XLOGF(
372+
DBG,
373+
"Checking touchpoints: {}",
374+
common::vecToString(
375+
tp.ts.openToParty(common::PUBLISHER).getValue()));
376+
} else {
377+
OMNISCIENT_ONLY_XLOGF(
378+
DBG,
379+
"Checking touchpoint: {}",
380+
tp.ts.openToParty(common::PUBLISHER).getValue());
289381
}
382+
383+
auto isTouchpointAttributable =
384+
attributionRule.isAttributable(tp, conv, threshold);
385+
386+
auto isAttributed = isTouchpointAttributable & !hasAttributedTouchpoint;
387+
388+
hasAttributedTouchpoint = isAttributed | hasAttributedTouchpoint;
389+
390+
if constexpr (usingBatch) {
391+
OMNISCIENT_ONLY_XLOGF(
392+
DBG,
393+
"isTouchpointAttributable={}, isAttributed={}, hasAttributedTouchpoint={}",
394+
common::vecToString(
395+
isTouchpointAttributable.extractBit().getValue()),
396+
common::vecToString(isAttributed.extractBit().getValue()),
397+
common::vecToString(
398+
hasAttributedTouchpoint.extractBit().getValue()));
399+
} else {
400+
OMNISCIENT_ONLY_XLOGF(
401+
DBG,
402+
"isTouchpointAttributable={}, isAttributed={}, hasAttributedTouchpoint={}",
403+
isTouchpointAttributable.extractBit().getValue(),
404+
isAttributed.extractBit().getValue(),
405+
hasAttributedTouchpoint.extractBit().getValue());
406+
}
407+
408+
attributedAdId = attributedAdId.mux(isAttributed, tp.adId);
290409
}
291-
std::reverse(attributions.begin(), attributions.end());
410+
attributionsOutput.push_back(
411+
AttributionReformattedOutputFmt<schedulerId, usingBatch>{
412+
.ad_id = attributedAdId,
413+
.conv_value = conv.convValue,
414+
.is_attributed = hasAttributedTouchpoint});
292415
}
293-
return attributions;
416+
std::reverse(attributionsOutput.begin(), attributionsOutput.end());
417+
return attributionsOutput;
294418
}
295419

296420
template <

fbpcs/emp_games/pcf2_attribution/test/AttributionGameTest.cpp

Lines changed: 0 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -245,23 +245,6 @@ TEST(AttributionGameTest, TestAttributionLogicPlaintext) {
245245
thresholdsLastTouch1D,
246246
1);
247247

248-
FLAGS_use_new_output_format = true;
249-
auto computeAttributionLastClick1DNewOutputFormat =
250-
game.computeAttributionsHelper(
251-
privateTouchpoints.at(0),
252-
privateConversions.at(0),
253-
*lastClick1D,
254-
thresholdsLastClick1D,
255-
1);
256-
257-
auto computeAttributionLastTouch1DNewOutputFormat =
258-
game.computeAttributionsHelper(
259-
privateTouchpoints.at(0),
260-
privateConversions.at(0),
261-
*lastTouch1D,
262-
thresholdsLastTouch1D,
263-
1);
264-
265248
for (size_t i = 0; i < attributionResultsLastClick1D.size(); ++i) {
266249
EXPECT_EQ(
267250
computeAttributionLastClick1D.at(i)
@@ -277,10 +260,6 @@ TEST(AttributionGameTest, TestAttributionLogicPlaintext) {
277260
.getValue(),
278261
attributionResultsLastTouch1D.at(i));
279262
}
280-
281-
EXPECT_EQ(computeAttributionLastClick1DNewOutputFormat.size(), 0);
282-
283-
EXPECT_EQ(computeAttributionLastTouch1DNewOutputFormat.size(), 0);
284263
}
285264

286265
TEST(AttributionGameTest, TestAttributionLogicPlaintextBatch) {
@@ -354,23 +333,6 @@ TEST(AttributionGameTest, TestAttributionLogicPlaintextBatch) {
354333
thresholdsLastTouch1D,
355334
batchSize);
356335

357-
FLAGS_use_new_output_format = true;
358-
auto computeAttributionLastClick1DNewOutputFormat =
359-
game.computeAttributionsHelper(
360-
privateTouchpoints,
361-
privateConversions,
362-
*lastClick1D,
363-
thresholdsLastClick1D,
364-
batchSize);
365-
366-
auto computeAttributionLastTouch1DNewOutputFormat =
367-
game.computeAttributionsHelper(
368-
privateTouchpoints,
369-
privateConversions,
370-
*lastTouch1D,
371-
thresholdsLastTouch1D,
372-
batchSize);
373-
374336
for (size_t i = 0; i < attributionResultsLastClick1D.size(); ++i) {
375337
for (size_t j = 0; j < batchSize; ++j) {
376338
EXPECT_EQ(
@@ -392,10 +354,6 @@ TEST(AttributionGameTest, TestAttributionLogicPlaintextBatch) {
392354
attributionResultsLastTouch1D.at(i));
393355
}
394356
}
395-
396-
EXPECT_EQ(computeAttributionLastClick1DNewOutputFormat.size(), 0);
397-
398-
EXPECT_EQ(computeAttributionLastTouch1DNewOutputFormat.size(), 0);
399357
}
400358

401359
template <

0 commit comments

Comments
 (0)