WICG / turtledove

TURTLEDOVE
https://wicg.github.io/turtledove/
Other
532 stars 235 forks source link

Reporting ad cost #356

Closed abrik0131 closed 1 year ago

abrik0131 commented 2 years ago

When a buyer (DSP) participates in the auction, the bid is often on a CPM basis: how much would the buyer pay per 1000 impressions? The buyer, however, often charges advertisers per click (CPC), per conversion (CPA) or on the basis of another event that may or may not occur after the impression is viewed.

Thus, the bid returned by generateBid() to participate in the auction is often not the same value as the advertiser click or conversion cost used by DSP for CPC or CPA billing. Converting the bid into advertiser click or conversion cost often requires values computed within generateBid(). The problem is that FLEDGE does not provide a mechanism for reporting values computed inside generateBid(), except for the bid itself.

This requires a DSP to compute advertiser click or conversion cost on the server from the reported data, often recomputing the same values that were computed within generateBid(). There are cases when recomputation could produce different results. For example if the advertiser click or conversion cost computation logic in generateBid() and on the server become out of sync, or if the versions of the models used in computing the bid and advertiser click or conversion cost on the client, become out of sync from those used on the server. There are solutions to these problems, such as using data version headers. Nevertheless, even when data version headers are used, CPC or CPA billing with FLEDGE remains complicated and error prone. Furthermore, server-side recomputation becomes impossible if user specific browser signals such as joinCount, bidCount or prevWins are used in the bid computation.

This in turn is a gap in FLEDGE in that while FLEDGE provides joinCount, bidCount or prevWins signals to the bidding functions, it does not provide the mechanism for reporting advertiser click or conversion cost without which using the signals for FLEDGE bidding is impossible.

Let’s consider what happens if the advertiser click or conversion cost computed on the client uses signals available on the client only, such as joinCount or bidCount (passed to generateBid() via browserSignals). Because neither joinCount nor bidCount are available on the server, recomputing advertiser click or conversion cost changes from being merely complicated to being very difficult or impossible. This, in turn, makes accurate advertiser CPC or CPA billing very difficult or impossible.

To solve this problem, we propose that generateBid() returns an additional value, which is adCost:

generateBid(interestGroup, auctionSignals, perBuyerSignals, trustedBiddingSignals, browserSignals) {
  ...
  return {'ad': adObject,
          'bid': bidValue,
          'adCost': adCost,
          'render': renderUrl,
          'adComponents': [adComponent1, adComponent2, ...],
          'allowComponentAuction': false};
}

adCost will be passed by the browser to reportWin() as part of the browserSignals. This will allow the buyer to report advertiser click or conversion cost accurately, and bill advertisers CPC or CPA accurately, even in the case when advertiser click or conversion cost computation uses signals available on the client only.

adCost signal thus passed to reportWin() is a privacy risk. To decrease the risk, the number of bits available as adCost could be limited.

JensenPaul commented 2 years ago

Is reporting of this adCost value only needed when there's a click? Your proposal to pass adCost to reportWin() would make the value available for every won auction, whereas it would be preferable from a privacy perspective to limit the adCost availability to only cases where a click occurs (e.g. by passing it via Fenced Frame Ads Reporting only after a click occurs).

JensenPaul commented 2 years ago

Does the adCost need to be a separate cost, or could it be expressed as a ratio/proportion of the bid? Expressing it as a ratio/proportion may facilitate expressing it in fewer bits than a separate cost (which could have a larger range).

michaelkleber commented 2 years ago

My instinct is that reporting a straight cost is better than a ratio:

  1. If the bidValue is a cost per impression while the adCost is a cost per click or per conversion, then it seems like their ratio could have a very large dynamic range. So this doesn't seem like it would save us bits.
  2. If we expect to noise the reported values, then noising a ratio requires some delicate handling on the other end — the product of two noised values has a noise distribution which depends also on the magnitude of the numbers being multiplied.

I would very much like to hear answers to Paul's previous question, whether it would be sufficient to report the adCost only after a click!

abrik0131 commented 2 years ago

There are several billing types that Google Ads would like to support. For click based ads adCost can be reported per click. However, for active view based ads we need adCost to be reported per active view. For impression based ads, adCost needs to be reported per impression.

Note that even for impression based ads, adCost is still needed to be reported as advertiser payout is not the same as bid, and may be difficult to reconstruct using bid and the signals available server-side after winner notification is received there.

JensenPaul commented 1 year ago

Given the prevalence of CPC and CPA and cost-per-active-view billed advertisements, and the risk of inaccuracy in later re-calculating the cost of the impression, it seems reasonable to pass an adCost from generateBid() to reportWin(). To limit the privacy impact, while event-level reporting is available, we’d like to limit the accuracy of the adCost value. The FLEDGE explainer already discusses limiting the accuracy of the bid and desirability values. Our plan is to do stochastic rounding of the bid, desirability and adCost floating point value’s mantissa to an 8-bit value. The rounding would be done immediately after scoreAd, before the values are passed to reportWin() and reportResult(). Limiting the floating point value’s exponent field also makes sense as it doesn’t seem useful to bid incredibly large or small amounts. For reference a 64-bit floating point value can represent values with 308 decimal digits. Our plan is to limit the exponent to 8 bits, like a 32-bit floating point value’s exponent.

I’ve put some examples of rounding a value’s mantissa to 8 bits in this sheet. The math used to do the rounding should be visible in the rows at the bottom of the sheet.

JensenPaul commented 1 year ago

This was addressed.