SyncAI.news, a Varaisys broadcasting
Meta AI Open-Sources Rebalancer: A C++ Assignment Solver That Runs About 40 Million Placement Problems a Day
AR

Asif Razzaq

· 8 min read

EngineeringMarkTechPost

Meta AI Open-Sources Rebalancer: A C++ Assignment Solver That Runs About 40 Million Placement Problems a Day

Meta has open-sourced Rebalancer, a C++ library with a Python interface for solving assignment problems. It decides which objects go into which bins under constraints and objectives. According to the Engineering at Meta’s post, Rebalancer has handled resource allocation across Meta for over 9 years. The release ships under Apache 2.0 with documentation, a PyPI package and a debugging UI called Rebalancer Explorer.

Is it deployable? Yes. pip install rebalancer installs v1.0.4 for Python 3.12+, with prebuilt wheels for Linux x86-64 and macOS 14+ ARM64. .deb, .rpm and Homebrew packages also exist. PyPI still classifies the project as Alpha.

What Problem Does Rebalancer Solve?

Assignment problems show up across Meta’s stack. Racks go into datacenters, servers go to services, tasks go to servers, and user traffic goes to datacenters. Meta names 2 blockers: usability and scalability. Engineers struggle to turn policies into precise formulas, and many problems are NP-hard and too large for commercial solvers.

Rebalancer’s answer is to separate how a problem is specified from how it is solved. The design is detailed in the OSDI 2024 paper, Optimizing Resource Allocation in Hyperscale Datacenters.

How the Specification Layer Works

The spec language has 3 layers:

  • Modeling constructs: dimensions (attributes such as CPU or storage), partitions (groups of objects), scopes (groups of bins) and utilization.
  • Expression API: aggregate utilization with SUM or MAX, or transform it with operations such as SQUARE.
  • Spec API: dozens of predefined objectives and constraints, listed in the docs.

Meta’s example models tasks as objects, servers as bins and racks as a scope. A CapacitySpec caps CPU and storage per server. A GroupCountSpec keeps 1 job type per rack. A BalanceSpec balances each server’s utilization across both dimensions.

One Expression Graph, Two Solvers

Rebalancer compiles the spec into a directed acyclic expression graph. Leaf nodes hold utilization values; aggregation and transformation nodes sit above them. Users supply an initial assignment and a stopping condition. Constraints that the initial assignment already violates become high-priority goals.

Optimal solver: The graph is translated into a mixed integer program for FICO Xpress, Gurobi or HiGHS. Variable aggregation and symmetry breaking shrink models. The worst-case model size is still O(objects × bins). Meta’s largest problems are too big for any MIP solver.

Local search: This solver works directly on the expression graph. It explores moves of objects to other bins, with a worst-case neighborhood of O(objects + bins). It then applies the best candidate that breaks no constraint. Evaluation is parallelized, reaching millions of evaluations per second, and the search space is pruned.

Meta uses local search for almost all large problems and MIP for small to mid-size ones, often prototyping with MIP first.

Production Numbers at Meta

  • About 40 million assignment problems solved per day, across 30+ unique formulations.
  • P99 solve time of 12 seconds on 265k objects and 3.2k bins.
  • Problems above 1 million objects and 5k bins average 171 seconds, across 3.4k+ runs.

Best Use Cases for Rebalancer

  1. Placing shards, tasks or containers on a cluster: Assign work to servers under CPU and memory caps while spreading replicas across racks. Meta’s Shard Manager and RAS run this pattern.
  2. Balancing traffic and workloads across regions: Route user traffic or jobs to datacenters, trading latency against load. Taiji does this for edge traffic, and Meta balances ML training by priority.
  3. Operational assignment outside infrastructure: Map support tickets to engineers, meetings to rooms or desks to people under capacity rules. Meta has done all 3.

Debugging With Rebalancer Explorer

Modelers at Meta spent most of their time debugging solver behavior. Rebalancer Explorer is a Dockerized web UI built for this. It shows binding constraints, relaxation effects, and why an object landed in a bin.

Interactive Explainer

Run local search&lt;/button&gt; &lt;button class=&quot;b&quot; id=&quot;mtpStep&quot;&gt;Step once&lt;/button&gt; &lt;button class=&quot;b&quot; id=&quot;mtpShuf&quot;&gt;Random bad start&lt;/button&gt; &lt;button class=&quot;b&quot; id=&quot;mtpReset&quot;&gt;Reset&lt;/button&gt; &lt;/div&gt; &lt;div class=&quot;stats&quot;&gt; &lt;div class=&quot;st&quot;&gt;&lt;div class=&quot;k&quot;&gt;Step&lt;/div&gt;&lt;div class=&quot;v&quot; id=&quot;mtpS&quot;&gt;0&lt;/div&gt;&lt;/div&gt; &lt;div class=&quot;st&quot;&gt;&lt;div class=&quot;k&quot;&gt;Moves evaluated&lt;/div&gt;&lt;div class=&quot;v&quot; id=&quot;mtpE&quot;&gt;0&lt;/div&gt;&lt;/div&gt; &lt;div class=&quot;st&quot;&gt;&lt;div class=&quot;k&quot;&gt;Capacity overflow&lt;/div&gt;&lt;div class=&quot;v&quot; id=&quot;mtpV&quot;&gt;0&lt;/div&gt;&lt;/div&gt; &lt;div class=&quot;st&quot;&gt;&lt;div class=&quot;k&quot;&gt;Balance objective&lt;/div&gt;&lt;div class=&quot;v&quot; id=&quot;mtpO&quot;&gt;0&lt;/div&gt;&lt;/div&gt; &lt;/div&gt; &lt;div class=&quot;racks&quot; id=&quot;mtpRacks&quot;&gt;&lt;/div&gt; &lt;div class=&quot;spark&quot;&gt;&lt;div class=&quot;k&quot;&gt;Objective over steps (lower is better, ideal = 324)&lt;/div&gt; &lt;svg id=&quot;mtpSpark&quot; viewBox=&quot;0 0 800 70&quot; width=&quot;100%&quot; height=&quot;70&quot; preserveAspectRatio=&quot;none&quot;&gt;&lt;/svg&gt; &lt;/div&gt; &lt;div class=&quot;log&quot; id=&quot;mtpLog&quot;&gt;Server S1 starts 4 CPU over capacity. Press Run.&lt;/div&gt; &lt;/div&gt; &lt;!-- PANE 2 --&gt; &lt;div class=&quot;pane&quot; id=&quot;mtpP1&quot;&gt; &lt;p class=&quot;note&quot;&gt;Rebalancer compiles specs into a &lt;b&gt;directed acyclic expression graph&lt;/b&gt;. Leaves hold each server's utilization; SQUARE, SUM and MAX nodes sit above them. When one task moves, only the leaves it touches and their ancestors need new values. The graph uses the same live state as tab 1.&lt;/p&gt; &lt;div class=&quot;btns&quot;&gt; &lt;button class=&quot;b pri&quot; id=&quot;mtpGMove&quot;&gt;Move a random task&lt;/button&gt; &lt;button class=&quot;b&quot; id=&quot;mtpGBest&quot;&gt;Apply best local-search move&lt;/button&gt; &lt;/div&gt; &lt;div class=&quot;gwrap&quot;&gt;&lt;svg id=&quot;mtpGraph&quot; viewBox=&quot;0 0 820 300&quot; width=&quot;100%&quot;&gt;&lt;/svg&gt;&lt;/div&gt; &lt;div class=&quot;log&quot; id=&quot;mtpGLog&quot;&gt;Press a button to move a task.&lt;/div&gt; &lt;/div&gt; &lt;!-- PANE 3 --&gt; &lt;div class=&quot;pane&quot; id=&quot;mtpP2&quot;&gt; &lt;p class=&quot;note&quot;&gt;The MIP model needs about one binary variable per object per bin, so it grows as &lt;b&gt;O(objects × bins)&lt;/b&gt;. A local-search neighborhood grows as &lt;b&gt;O(objects + bins)&lt;/b&gt;. Drag the sliders or load Meta's published sizes.&lt;/p&gt; &lt;div class=&quot;btns&quot;&gt; &lt;button class=&quot;b&quot; data-o=&quot;500&quot; data-bn=&quot;20&quot;&gt;Small: 500 × 20&lt;/button&gt; &lt;button class=&quot;b&quot; data-o=&quot;265000&quot; data-bn=&quot;3200&quot;&gt;Meta P99: 265k × 3.2k&lt;/button&gt; &lt;button class=&quot;b&quot; data-o=&quot;1000000&quot; data-bn=&quot;5000&quot;&gt;Meta XL: 1M × 5k&lt;/button&gt; &lt;/div&gt; &lt;div class=&quot;sl&quot;&gt;&lt;label&gt;Objects &lt;b id=&quot;mtpOv&quot;&gt;&lt;/b&gt;&lt;/label&gt;&lt;input type=&quot;range&quot; id=&quot;mtpOs&quot; min=&quot;1&quot; max=&quot;6.3&quot; step=&quot;0.01&quot; value=&quot;2.7&quot;&gt;&lt;/div&gt; &lt;div class=&quot;sl&quot;&gt;&lt;label&gt;Bins &lt;b id=&quot;mtpBv&quot;&gt;&lt;/b&gt;&lt;/label&gt;&lt;input type=&quot;range&quot; id=&quot;mtpBs&quot; min=&quot;0.3&quot; max=&quot;4&quot; step=&quot;0.01&quot; value=&quot;1.3&quot;&gt;&lt;/div&gt; &lt;div class=&quot;cmp&quot;&gt; &lt;div class=&quot;row&quot;&gt;&lt;div class=&quot;t&quot;&gt;MIP binary variables, worst case &lt;span id=&quot;mtpMv&quot;&gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;track&quot;&gt;&lt;i id=&quot;mtpMb&quot; style=&quot;background:linear-gradient(90deg,#FF8A5A,#FF5A6E)&quot;&gt;&lt;/i&gt;&lt;/div&gt;&lt;/div&gt; &lt;div class=&quot;row&quot;&gt;&lt;div class=&quot;t&quot;&gt;Local-search neighborhood, worst case &lt;span id=&quot;mtpLv&quot;&gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;track&quot;&gt;&lt;i id=&quot;mtpLb&quot; style=&quot;background:linear-gradient(90deg,#0866FF,#38D6FF)&quot;&gt;&lt;/i&gt;&lt;/div&gt;&lt;/div&gt; &lt;/div&gt; &lt;div class=&quot;verdict&quot; id=&quot;mtpVerdict&quot;&gt;&lt;/div&gt; &lt;p class=&quot;note&quot; style=&quot;margin-top:10px&quot;&gt;Bars use a log scale. Size bands in the verdict are illustrative; Meta's stated rule is local search for almost all large problems and MIP for small to mid-size ones.&lt;/p&gt; &lt;/div&gt; &lt;!-- PANE 4 --&gt; &lt;div class=&quot;pane&quot; id=&quot;mtpP3&quot;&gt; &lt;p class=&quot;note&quot;&gt;Production figures published by Meta for Rebalancer (Engineering at Meta, Sep 21 2026).&lt;/p&gt; &lt;div class=&quot;grid&quot; id=&quot;mtpCards&quot;&gt; &lt;div class=&quot;card&quot;&gt;&lt;div class=&quot;n&quot;&gt;&lt;span data-c=&quot;40&quot;&gt;0&lt;/span&gt;&lt;em&gt;M&lt;/em&gt;&lt;/div&gt;&lt;div class=&quot;d&quot;&gt;assignment problems solved per day&lt;/div&gt;&lt;/div&gt; &lt;div class=&quot;card&quot;&gt;&lt;div class=&quot;n&quot;&gt;&lt;span data-c=&quot;30&quot;&gt;0&lt;/span&gt;&lt;em&gt;+&lt;/em&gt;&lt;/div&gt;&lt;div class=&quot;d&quot;&gt;unique problem formulations&lt;/div&gt;&lt;/div&gt; &lt;div class=&quot;card&quot;&gt;&lt;div class=&quot;n&quot;&gt;&lt;span data-c=&quot;12&quot;&gt;0&lt;/span&gt;&lt;em&gt;s&lt;/em&gt;&lt;/div&gt;&lt;div class=&quot;d&quot;&gt;P99 solve time on 265k objects and 3.2k bins&lt;/div&gt;&lt;/div&gt; &lt;div class=&quot;card&quot;&gt;&lt;div class=&quot;n&quot;&gt;&lt;span data-c=&quot;171&quot;&gt;0&lt;/span&gt;&lt;em&gt;s&lt;/em&gt;&lt;/div&gt;&lt;div class=&quot;d&quot;&gt;average solve for 1M+ objects and 5k bins&lt;/div&gt;&lt;/div&gt; &lt;div class=&quot;card&quot;&gt;&lt;div class=&quot;n&quot;&gt;&lt;span data-c=&quot;3.4&quot; data-d=&quot;1&quot;&gt;0&lt;/span&gt;&lt;em&gt;k+&lt;/em&gt;&lt;/div&gt;&lt;div class=&quot;d&quot;&gt;runs at that 1M+ object scale&lt;/div&gt;&lt;/div&gt; &lt;div class=&quot;card&quot;&gt;&lt;div class=&quot;n&quot;&gt;&lt;span data-c=&quot;9&quot;&gt;0&lt;/span&gt;&lt;em&gt;+ yrs&lt;/em&gt;&lt;/div&gt;&lt;div class=&quot;d&quot;&gt;in use across Meta before open-sourcing&lt;/div&gt;&lt;/div&gt; &lt;/div&gt; &lt;div class=&quot;pills&quot;&gt; &lt;span class=&quot;pill&quot;&gt;Shard Manager: shards → servers&lt;/span&gt; &lt;span class=&quot;pill&quot;&gt;RAS: servers → services&lt;/span&gt; &lt;span class=&quot;pill&quot;&gt;Taiji: edge traffic → datacenters&lt;/span&gt; &lt;span class=&quot;pill&quot;&gt;Serverless function grouping&lt;/span&gt; &lt;span class=&quot;pill&quot;&gt;ML training balancing&lt;/span&gt; &lt;span class=&quot;pill&quot;&gt;Meetings → rooms&lt;/span&gt; &lt;/div&gt; &lt;/div&gt; &lt;div class=&quot;ft&quot;&gt; &lt;span&gt;Sources: &lt;a href=&quot;https://engineering.fb.com/2026/09/21/open-source/rebalancer-generic-high-performance-library-assignment-problems/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Engineering at Meta&lt;/a&gt; · &lt;a href=&quot;https://github.com/facebook/rebalancer&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;GitHub&lt;/a&gt; · Tabs 1 to 3 are simplified simulations&lt;/span&gt; &lt;span class=&quot;brand&quot;&gt;Built by Marktechpost&lt;/span&gt; &lt;/div&gt; &lt;/div&gt; &lt;script&gt; (function(){ var R=document.getElementById('mtp-rebal'); function postH(){try{parent.postMessage({mtpRebalH:R.offsetHeight+40},'*');}catch(e){}} var SIZES=[6,5,4,4,3,3,3,2,2,2,1,1], CAP=16, NS=4; var START=[0,0,0,0,1,1,1,2,2,2,0,1]; var asg=START.slice(), step=0, evals=0, hist=[], timer=null, hot=-1; function util(a){var u=[0,0,0,0];for(var i=0;i&lt;a.length;i++)u[a[i]]+=SIZES[i];return u;} function score(a){var u=util(a),v=0,o=0;for(var s=0;s&lt;NS;s++){v+=Math.max(0,u[s]-CAP);o+=u[s]*u[s];}return [v,o];} function better(x,y){return x[0]&lt;y[0]||(x[0]===y[0]&amp;&amp;x[1]&lt;y[1]);} function bestMove(){ var cur=score(asg),best=null,bs=cur,n=0; for(var i=0;i&lt;asg.length;i++){for(var s=0;s&lt;NS;s++){if(s===asg[i])continue;var b=asg.slice();b[i]=s;n++;var sc=score(b);if(better(sc,bs)){bs=sc;best={a:b,txt:'move t'+(i+1)+' S'+(asg[i]+1)+' → S'+(s+1),ids:[i]};}}} for(var i2=0;i2&lt;asg.length;i2++)for(var j=i2+1;j&lt;asg.length;j++){if(asg[i2]===asg[j])continue;var c=asg.slice();c[i2]=asg[j];c[j]=asg[i2];n++;var sc2=score(c);if(better(sc2,bs)){bs=sc2;best={a:c,txt:'swap t'+(i2+1)+' <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2194.png" alt="↔" class="wp-smiley" style="height: 1em; max-height: 1em;" /> t'+(j+1),ids:[i2,j]};}} return {best:best,n:n,sc:bs}; } var racksEl=document.getElementById('mtpRacks'); function rects(){var m={};racksEl.querySelectorAll('.chip').forEach(function(c){m[c.dataset.id]=c.getBoundingClientRect();});return m;} function render(ids){ var before=rects(),u=util(asg),h=''; for(var r=0;r&lt;2;r++){h+='&lt;div class=&quot;rack&quot;&gt;&lt;div class=&quot;rl&quot;&gt;Rack '+(r?'B':'A')+' (scope)&lt;/div&gt;&lt;div class=&quot;srvs&quot;&gt;'; for(var s=r*2;s&lt;r*2+2;s++){var over=u[s]&gt;CAP; h+='&lt;div class=&quot;srv'+(over?' over':'')+'&quot;&gt;&lt;div class=&quot;sn&quot;&gt;S'+(s+1)+'&lt;span&gt;'+u[s]+' / '+CAP+' CPU&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;bar&quot;&gt;&lt;i style=&quot;width:'+Math.min(100,u[s]/24*100)+'%&quot;&gt;&lt;/i&gt;&lt;u style=&quot;left:'+(CAP/24*100)+'%&quot;&gt;&lt;/u&gt;&lt;/div&gt;&lt;div class=&quot;chips&quot;&gt;'; for(var i=0;i&lt;asg.length;i++)if(asg[i]===s){h+='&lt;div class=&quot;chip'+(ids&amp;&amp;ids.indexOf(i)&gt;-1?' hot':'')+'&quot; data-id=&quot;'+i+'&quot; style=&quot;width:'+(30+SIZES[i]*8)+'px&quot;&gt;t'+(i+1)+'·'+SIZES[i]+'&lt;/div&gt;';} h+='&lt;/div&gt;&lt;/div&gt;';} h+='&lt;/div&gt;&lt;/div&gt;';} racksEl.innerHTML=h; racksEl.querySelectorAll('.chip').forEach(function(c){var b=before[c.dataset.id];if(!b)return;var a=c.getBoundingClientRect(),dx=b.left-a.left,dy=b.top-a.top;if(dx||dy){c.style.transition='none';c.style.transform='translate('+dx+'px,'+dy+'px)';requestAnimationFrame(function(){requestAnimationFrame(function(){c.style.transition='transform .55s cubic-bezier(.2,.8,.2,1)';c.style.transform='';});});}}); var sc=score(asg); document.getElementById('mtpS').textContent=step; document.getElementById('mtpE').textContent=evals; var V=document.getElementById('mtpV');V.textContent=sc[0];V.className='v '+(sc[0]?'bad':'good'); var O=document.getElementById('mtpO');O.textContent=sc[1];O.className='v'+(sc[1]===324?' good':''); spark();drawGraph(null);postH(); } function spark(){ var s=document.getElementById('mtpSpark'),pts=hist.length?hist:[score(asg)[1]]; var mx=Math.max.apply(null,pts.concat([600])),mn=300,n=Math.max(pts.length-1,12); var p=pts.map(function(v,i){return (i/n*790+5)+','+(65-(v-mn)/(mx-mn)*58);}).join(' '); var ideal=65-(324-mn)/(mx-mn)*58; s.innerHTML='&lt;line x1=&quot;0&quot; x2=&quot;800&quot; y1=&quot;'+ideal+'&quot; y2=&quot;'+ideal+'&quot; stroke=&quot;#2BD99F&quot; stroke-dasharray=&quot;4 4&quot; opacity=&quot;.6&quot;/&gt;&lt;polyline points=&quot;'+p+'&quot; fill=&quot;none&quot; stroke=&quot;#38D6FF&quot; stroke-width=&quot;2.5&quot;/&gt;'+pts.map(function(v,i){return '&lt;circle cx=&quot;'+(i/n*790+5)+'&quot; cy=&quot;'+(65-(v-mn)/(mx-mn)*58)+'&quot; r=&quot;3.5&quot; fill=&quot;#0866FF&quot; stroke=&quot;#fff&quot; stroke-width=&quot;1&quot;/&gt;';}).join(''); } var logEl=document.getElementById('mtpLog'); function doStep(){ var r=bestMove();evals+=r.n; if(!r.best){stop();logEl.textContent='Local optimum: no move or swap improves the score ('+evals+' evaluations).';render();return false;} var old=asg;asg=r.best.a;step++;hist.push(score(asg)[1]); var moved=r.best.ids;logEl.textContent='Step '+step+': '+r.best.txt+' (checked '+r.n+' candidates)'; render(moved);drawGraph(diff(old,asg));return true; } function diff(a,b){var s={};for(var i=0;i&lt;a.length;i++)if(a[i]!==b[i]){s[a[i]]=1;s[b[i]]=1;}return Object.keys(s).map(Number);} function stop(){if(timer){clearInterval(timer);timer=null;}document.getElementById('mtpRun').textContent='<img src="https://s.w.org/images/core/emoji/17.0.2/72x72/25b6.png" alt="▶" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Run local search';} document.getElementById('mtpRun').onclick=function(){if(timer){stop();return;}this.textContent='❚❚ Pause';if(!doStep())return;timer=setInterval(function(){if(!doStep())stop();},900);}; document.getElementById('mtpStep').onclick=function(){stop();doStep();}; function resetTo(a,msg){stop();asg=a;step=0;evals=0;hist=[score(asg)[1]];logEl.textContent=msg;render();} document.getElementById('mtpReset').onclick=function(){resetTo(START.slice(),'Server S1 starts 4 CPU over capacity. Press Run.');}; document.getElementById('mtpShuf').onclick=function(){var a=[];for(var i=0;i&lt;12;i++)a.push(Math.random()&lt;.55?Math.floor(Math.random()*2):Math.floor(Math.random()*4));resetTo(a,'New random start. Press Run.');}; /* expression graph */ var G=document.getElementById('mtpGraph'),gLog=document.getElementById('mtpGLog'); var LX=[110,300,490,680]; function drawGraph(live){ live=live||[];var u=util(asg),sc=score(asg),h=''; function on(s){return live.indexOf(s)&gt;-1;} var any=live.length&gt;0; for(var s=0;s&lt;4;s++){ h+='&lt;path class=&quot;edge'+(on(s)?' live':'')+'&quot; d=&quot;M'+LX[s]+',232 L'+LX[s]+',172&quot;/&gt;'; h+='&lt;path class=&quot;edge'+(on(s)?' live':'')+'&quot; d=&quot;M'+LX[s]+',138 C'+LX[s]+',100 300,110 300,78&quot;/&gt;'; h+='&lt;path class=&quot;edge'+(on(s)?' live':'')+'&quot; d=&quot;M'+(LX[s]+40)+',233 C'+(LX[s]+60)+',200 650,130 650,78&quot;/&gt;'; } h+='&lt;path class=&quot;edge'+(any?' live':'')+'&quot; d=&quot;M300,44 L300,22&quot;/&gt;&lt;path class=&quot;edge'+(any?' live':'')+'&quot; d=&quot;M650,44 L650,22&quot;/&gt;'; function node(x,y,w,label,val,l){return '&lt;g class=&quot;node'+(l?' live':'')+'&quot;&gt;&lt;rect x=&quot;'+(x-w/2)+'&quot; y=&quot;'+(y-17)+'&quot; width=&quot;'+w+'&quot; height=&quot;34&quot; rx=&quot;8&quot;/&gt;&lt;text x=&quot;'+x+'&quot; y=&quot;'+(y-2)+'&quot;&gt;'+label+'&lt;/text&gt;&lt;text class=&quot;val&quot; x=&quot;'+x+'&quot; y=&quot;'+(y+11)+'&quot;&gt;'+val+'&lt;/text&gt;&lt;/g&gt;';} for(var k=0;k&lt;4;k++){h+=node(LX[k],250,96,'U(S'+(k+1)+')','= '+u[k],on(k));h+=node(LX[k],155,96,'SQUARE','= '+u[k]*u[k],on(k));} h+=node(300,61,120,'SUM','= '+sc[1],any);h+=node(650,61,120,'MAX','= '+Math.max.apply(null,u),any); h+='&lt;text x=&quot;300&quot; y=&quot;14&quot; fill=&quot;#4C9BFF&quot; font-size=&quot;11&quot; font-weight=&quot;700&quot; text-anchor=&quot;middle&quot;&gt;BalanceSpec objective&lt;/text&gt;'; h+='&lt;text x=&quot;650&quot; y=&quot;14&quot; fill=&quot;'+(Math.max.apply(null,u)&gt;CAP?'#FF5A6E':'#2BD99F')+'&quot; font-size=&quot;11&quot; font-weight=&quot;700&quot; text-anchor=&quot;middle&quot;&gt;CapacitySpec: MAX ≤ '+CAP+(Math.max.apply(null,u)&gt;CAP?' (violated)':' (ok)')+'&lt;/text&gt;'; h+='&lt;text x=&quot;410&quot; y=&quot;292&quot; fill=&quot;#93A3BF&quot; font-size=&quot;11&quot; text-anchor=&quot;middle&quot;&gt;Leaves: utilization per server · nodes recomputed this move: '+(any?(live.length*2+2):0)+' of 10&lt;/text&gt;'; G.innerHTML=h; } document.getElementById('mtpGMove').onclick=function(){stop();var i=Math.floor(Math.random()*12),s;do{s=Math.floor(Math.random()*4);}while(s===asg[i]);var old=asg;asg=asg.slice();asg[i]=s;evals++;step++;hist.push(score(asg)[1]);render([i]);var d=diff(old,asg);drawGraph(d);gLog.textContent='Moved t'+(i+1)+' S'+(old[i]+1)+' → S'+(s+1)+'. Only '+(d.length*2+2)+' of 10 nodes needed new values.';}; document.getElementById('mtpGBest').onclick=function(){stop();var r=bestMove();evals+=r.n;if(!r.best){gLog.textContent='Local optimum reached. Try a random move first.';render();return;}var old=asg;asg=r.best.a;step++;hist.push(score(asg)[1]);render(r.best.ids);var d=diff(old,asg);drawGraph(d);gLog.textContent='Best of '+r.n+' candidates: '+r.best.txt+'. Recomputed '+(d.length*2+2)+' of 10 nodes.';}; /* solver picker */ var Os=document.getElementById('mtpOs'),Bs=document.getElementById('mtpBs'); function fmt(n){if(n&gt;=1e9)return (n/1e9).toFixed(n&gt;=1e10?0:1)+'B';if(n&gt;=1e6)return (n/1e6).toFixed(n&gt;=1e7?0:1)+'M';if(n&gt;=1e3)return (n/1e3).toFixed(n&gt;=1e4?0:1)+'k';return Math.round(n)+'';} function pick(){ var o=exactO||Math.round(Math.pow(10,+Os.value)),b=exactB||Math.round(Math.pow(10,+Bs.value)),m=o*b,l=o+b;exactO=exactB=0; document.getElementById('mtpOv').textContent=fmt(o);document.getElementById('mtpBv').textContent=fmt(b); document.getElementById('mtpMv').textContent='≈ '+fmt(m);document.getElementById('mtpLv').textContent='≈ '+fmt(l); document.getElementById('mtpMb').style.width=Math.min(100,Math.log10(m)/11*100)+'%'; document.getElementById('mtpLb').style.width=Math.min(100,Math.log10(l)/11*100)+'%'; var v=document.getElementById('mtpVerdict'),t; if(m&lt;=1e6){t='&lt;b&gt;Optimal (MIP) solver is a good start.&lt;/b&gt; Small model: hand it to HiGHS, Gurobi or FICO Xpress and get a provably optimal assignment.';v.style.borderColor='#2BD99F';} else if(m&lt;=1e8){t='&lt;b&gt;Prototype with MIP, then move to local search.&lt;/b&gt; Meta says this is a common path: find a strong baseline with the optimal solver, then migrate.';v.style.borderColor='#4C9BFF';} else{t='&lt;b&gt;Local search.&lt;/b&gt; The MIP would need ≈ '+fmt(m)+' binary variables in the worst case, while each local-search neighborhood stays near '+fmt(l)+'. Meta runs almost all large problems this way.';v.style.borderColor='#38D6FF';} v.innerHTML=t;postH(); } var exactO=0,exactB=0;Os.oninput=pick;Bs.oninput=pick; document.querySelectorAll('#mtpP2 button[data-o]').forEach(function(btn){btn.onclick=function(){exactO=+btn.dataset.o;exactB=+btn.dataset.bn;Os.value=Math.log10(+btn.dataset.o);Bs.value=Math.log10(+btn.dataset.bn);pick();};}); /* stats */ var counted=false; function count(){ var cards=document.querySelectorAll('#mtpCards .card'); cards.forEach(function(c,i){c.classList.remove('in');setTimeout(function(){c.classList.add('in');},90*i);}); document.querySelectorAll('#mtpCards [data-c]').forEach(function(el){var tgt=+el.dataset.c,dp=+(el.dataset.d||0),t0=null; function f(ts){if(!t0)t0=ts;var p=Math.min(1,(ts-t0)/1100),e=1-Math.pow(1-p,3);el.textContent=(tgt*e).toFixed(dp);if(p&lt;1)requestAnimationFrame(f);}requestAnimationFrame(f);}); } /* tabs */ var tabs=R.querySelectorAll('.tab'); tabs.forEach(function(t){t.onclick=function(){tabs.forEach(function(x){x.classList.remove('on');});t.classList.add('on'); R.querySelectorAll('.pane').forEach(function(p){p.classList.remove('on');});document.getElementById('mtpP'+t.dataset.p).classList.add('on'); if(t.dataset.p==='3')count();if(t.dataset.p==='1')drawGraph(null);setTimeout(postH,60);setTimeout(postH,450);};}); hist=[score(asg)[1]];render();pick(); window.addEventListener('load',postH);window.addEventListener('resize',postH);setTimeout(postH,300); })(); &lt;/script&gt; &lt;/body&gt;&lt;/html&gt;">

Rebalancer vs Closest Open Source Alternatives

OR-Tools covers more problem classes, and Timefold targets JVM scheduling and routing. Rebalancer’s edge is one assignment spec that runs on both local search and MIP.

Key Takeaways

  • Rebalancer models any assignment problem as objects, bins, constraints and objectives.
  • Specs compile into an expression graph solved by local search or a MIP solver.
  • MIP backends include FICO Xpress, Gurobi and open source HiGHS.
  • Meta runs about 40M problems a day; P99 is 12s on 265k objects and 3.2k bins.
  • Apache 2.0, C++ and Python APIs, installable from PyPI today.

Original source

This story was published by MarkTechPost and written by Asif Razzaq. SyncAI.news shows a preview; the complete article is on the publisher's site.

Read the full story on marktechpost.com

Similar News