At Marin Software, advertisers spent defined monthly budgets across dozens of campaigns on Google, Meta, and Amazon — and routinely missed their targets. Allocation was manual, pacing was reactive, and forecasting was stale. I designed and led the development of Ascend: an ML system that automated the full cycle — forecast, allocate, pace, adjust — daily, across publishers.
The challenge
The surface problem was budget pacing: land monthly spend within tolerance. The real problem was harder — allocate budget *across* campaigns in a portfolio to maximise revenue, when each campaign has a different return curve and those curves shift daily. Marin's legacy forecasting used keyword-level bid-to-CPC and CPC-to-click models. That approach was dying. Google Smart Bidding was replacing manual bidding, which meant we lost visibility into actual bids. No bids, no bid-based models. Performance Max campaigns didn't even have keywords. We needed forecasting that didn't depend on bid as a predictor — a fundamentally different approach. The optimisation constraints made it worse. Campaigns were grouped by business line, funnel stage, or geography, each with its own efficiency targets. The publisher controls we had to manipulate varied wildly — keyword bids, group-level targets, campaign-level Smart Bidding targets, daily budget caps — across every major ad platform.
The approach
I was responsible for the technical design, algorithm selection, and research direction. I worked with a team of engineers, data scientists, and QA engineers, and closely with the Chief of Staff on product positioning and customer rollout. **Hierarchical campaign-level forecasting.** We replaced keyword models with multi-output regression at the campaign level. The core question: given a change in target for a campaign, what happens to spend, clicks, and conversions over the next N days? The model hierarchy was the key design decision:
- Campaign-level models — where sufficient data existed, we trained per-campaign models with one-hot-encoded campaign IDs. These captured campaign-specific response curves and produced the lowest errors.
- Customer-level fallback — for campaigns lacking history, we fell back to a model trained across that customer's campaigns, dropping campaign-specific encoding.
- Global fallback — for cold-start scenarios, a normalised global model produced reasonable estimates until local data accumulated.
Each model generated simulation curves: predicted spend, clicks, and conversions at different target levels for each day in the forecast window. Daily retraining by design. We trained Random Forest regressors via scikit-learn, with a pipeline that retrained from scratch every day with one more day of data. Rather than building complex model-monitoring and retuning infrastructure, we engineered the staleness problem away entirely — fresh models, fresh hyperparameters, every morning. Spend-weighted symmetric error. Standard error metrics didn't capture what mattered. A 10% error on a campaign spending £100k/month is more consequential than the same error at £500. We designed a custom metric — spend-weighted symmetric MAPE — where over- and under-forecasting were penalised equally, because both cause misallocation. Constrained optimisation via dual decomposition. With forecast curves in hand, the allocation problem becomes: distribute a fixed monthly budget across N campaigns to maximise total revenue, subject to constraints. We assumed campaigns were independent — not strictly true when audiences overlap, but the coupling was weak enough that the assumption held without sacrificing tractable computation. The solver used dual decomposition to break the problem into per-campaign subproblems connected through shared budget constraints, iterated until convergence. Efficient enough to run daily across full portfolios. Automated target-pushing and pacing. The optimiser's output — campaign-level targets — was pushed directly to publisher APIs daily. Intraday pacing monitored actual spend against the planned trajectory and adjusted accordingly. We built the analytics layer on Amazon QuickSight, integrated directly into the existing application — a pragmatic choice that let the product team own dashboard design without blocking on engineering release cycles.
The results
Ascend managed over $78M in annual spend across 32 customers. Budget targets were hit consistently. Revenue increased for equivalent spend levels — the optimiser found allocations that human analysts missed. Hours of weekly spreadsheet work per portfolio became automated daily adjustments. Most importantly, top-spending customers adopted and continued using the system month after month.
What made this hard
The forecasting wasn't the hardest part — making it *useful* was. A model that predicts well on average but badly on high-spend campaigns is worse than no model. That's why the spend-weighted error metric was load-bearing. The operational complexity was underestimated early on. Every publisher has different controls, update frequencies, and reporting lag. Building a unified optimisation layer across that fragmentation required as much API plumbing as algorithmic work. And visualising decisions for non-technical stakeholders — account managers handing budget control to an algorithm they needed to interrogate — was solved iteratively. The dashboards were as important to adoption as the maths.

