Meta 已开源 再平衡器,这是一个用于解决分配问题的带有 Python 接口的 C++ 库。它在约束和目标条件下决定哪些对象放入哪些箱。根据 Meta 工程团队的帖子,Rebalancer 已在 Meta 处理资源分配超过 9 年。此次发布以 Apache 2.0 许可证发布,附带 文档、一个 PyPI 包 以及一个名为 Rebalancer Explorer 的调试界面。
它可部署吗? 可以。 pip install rebalancer 为 Python 3.12+ 安装 v1.0.4,并为 Linux x86-64 和 macOS 14+ ARM64 提供预构建的 wheel。同时也有 .deb、.rpm 和 Homebrew 包。PyPI 仍将该项目的状态归类为 Alpha。
Rebalancer 解决什么问题?
分配问题在 Meta 的技术栈中无处不在。机架进入数据中心,服务器分配给服务,任务分配给服务器,用户流量分配到数据中心。Meta 指出了 2 个障碍:易用性和可扩展性。工程师难以将策略转化为精确的公式,而且许多问题是 NP 难的,规模也大到商业求解器无法处理。
Rebalancer 的答案是将问题的指定方式与求解方式分离。该设计在 OSDI 2024 论文, Optimizing Resource Allocation in Hyperscale Datacenters.
规范层如何工作
该规范语言有 3 层:
- 建模构造: 维度(如 CPU 或存储等属性)、分区(对象组)、作用域(bin 组)和利用率。
- 表达式 API: 使用 SUM 或 MAX 聚合利用率,或使用 SQUARE 等操作对其进行变换。
- Spec API: 数十个预定义的目标和约束,列在 文档.
Meta 的示例将任务建模为对象,服务器建模为 bin,机架建模为一个作用域。一个 CapacitySpec 限制每台服务器的 CPU 和存储。一个 GroupCountSpec 保证每个机架只有 1 种作业类型。一个 BalanceSpec 在两个维度上平衡每个服务器的利用率。
一个表达式图,两个求解器
Rebalancer 将规格编译为有向无环表达式图。叶节点保存利用率值;聚合和转换节点位于其上。用户提供初始分配和停止条件。初始分配已经违反的约束成为高优先级目标。
最优求解器:该图被转换为混合整数规划,用于 FICO Xpress, Gurobi 或 HiGHS。变量聚合和对称性破坏可缩小模型规模。最坏情况下的模型规模仍为 O(objects × bins)。Meta 最大的问题对任何 MIP 求解器来说都太大了。
局部搜索:该求解器直接在表达式图上工作。它探索将对象移动到其他 bin 的动作,最坏情况邻域为 O(objects + bins)。然后应用不违反任何约束的最佳候选。评估被并行化,达到每秒数百万次评估,并且搜索空间会被剪枝。
Meta 几乎对所有大型问题使用局部搜索,对中小型问题使用 MIP,通常先用 MIP 进行原型验证。
Meta 的生产数据
- 每天解决约 40 百万个分配问题,涵盖 30 多种不同的表述。
- 在 265k 个对象和 3.2k 个 bin 上,P99 求解时间为 12 秒。
- 超过 1 百万个对象和 5k 个 bin 的问题在 3.4k 多次运行中平均耗时 171 秒。
Rebalancer 的最佳使用场景
- 在集群上放置分片、任务或容器:在 CPU 和内存上限下将工作分配给服务器,同时将副本分散到不同机架。Meta 的 Shard Manager 和 RAS 运行这种模式。
- 跨区域平衡流量和工作负载:将用户流量或作业路由到数据中心,在延迟和负载之间进行权衡。 Taiji 为边缘流量执行此操作,Meta 则按优先级平衡 ML 训练。
- 基础设施之外的运营分配:在容量规则下,将支持工单映射到工程师、会议映射到房间、工位映射到人员。Meta 已完成全部这 3 项。
用 Rebalancer Explorer 调试
Meta 的建模者把大部分时间花在调试求解器行为上。 Rebalancer Explorer 就是为此构建的一个 Docker 化的网页 UI。它展示绑定约束、松弛效果,以及某个对象为何被放入某个箱。
交互式讲解
运行局部搜索</button> <button class="b" id="mtpStep">单步执行</button> <button class="b" id="mtpShuf">随机糟糕起点</button> <button class="b" id="mtpReset">重置</button> </div> <div class="stats"> <div class="st"><div class="k">步数</div><div class="v" id="mtpS">0</div></div> <div class="st"><div class="k">已评估的移动</div><div class="v" id="mtpE">0</div></div> <div class="st"><div class="k">容量溢出</div><div class="v" id="mtpV">0</div></div> <div class="st"><div class="k">均衡目标值</div><div class="v" id="mtpO">0</div></div> </div> <div class="racks" id="mtpRacks"></div> <div class="spark"><div class="k">各步骤的目标值(越低越好,理想值 = 324)</div> <svg id="mtpSpark" viewBox="0 0 800 70" width="100%" height="70" preserveAspectRatio="none"></svg> </div> <div class="log" id="mtpLog">服务器 S1 初始超载 4 个 CPU。请点击运行。</div> </div> <!-- PANE 2 --> <div class="pane" id="mtpP1"> <p class="note">Rebalancer 将规格编译为一个<b>有向无环表达式图</b>。叶子节点保存每台服务器的利用率;SQUARE、SUM 和 MAX 节点位于其上方。当一个任务移动时,只有它涉及的叶子节点及其祖先节点需要计算新值。该图与标签页 1 使用相同的实时状态。</p> <div class="btns"> <button class="b pri" id="mtpGMove">移动一个随机任务</button> <button class="b" id="mtpGBest">应用最佳局部搜索移动</button> </div> <div class="gwrap"><svg id="mtpGraph" viewBox="0 0 820 300" width="100%"></svg></div> <div class="log" id="mtpGLog">请点击按钮以移动一个任务。</div> </div> <!-- PANE 3 --> <div class="pane" id="mtpP2"> <p class="note">MIP 模型对每个容器中的每个对象大约需要一个二元变量,因此其规模以 <b>O(objects × bins)</b> 增长。局部搜索邻域的规模以 <b>O(objects + bins)</b> 增长。拖动滑块或加载 Meta 公布的规模。</p> <div class="btns"> <button class="b" data-o="500" data-bn="20">小型:500 × 20</button> <button class="b" data-o="265000" data-bn="3200">Meta P99:265k × 3.2k</button> <button class="b" data-o="1000000" data-bn="5000">Meta XL:1M × 5k</button> </div> <div class="sl"><label>对象 <b id="mtpOv"></b></label><input type="range" id="mtpOs" min="1" max="6.3" step="0.01" value="2.7"></div> <div class="sl"><label>容器 <b id="mtpBv"></b></label><input type="range" id="mtpBs" min="0.3" max="4" step="0.01" value="1.3"></div> <div class="cmp"> <div class="row"><div class="t">MIP 二元变量,最坏情况 <span id="mtpMv"></span></div><div class="track"><i id="mtpMb" style="background:linear-gradient(90deg,#FF8A5A,#FF5A6E)"></i></div></div> <div class="row"><div class="t">局部搜索邻域,最坏情况 <span id="mtpLv"></span></div><div class="track"><i id="mtpLb" style="background:linear-gradient(90deg,#0866FF,#38D6FF)"></i></div></div> </div> <div class="verdict" id="mtpVerdict"></div> <p class="note" style="margin-top:10px">柱状图使用对数刻度。结论中的规模区间仅作说明之用;Meta 所述的规则是,几乎所有大型问题使用局部搜索,而小型至中型问题使用 MIP。</p> </div> <!-- PANE 4 --> <div class="pane" id="mtpP3"> <p class="note">Meta 为 Rebalancer 发布的生产数据(Engineering at Meta,2026年9月21日)。</p> <div class="grid" id="mtpCards"> <div class="card"><div class="n"><span data-c="40">0</span><em>M</em></div><div class="d">每天求解的分配问题数量</div></div> <div class="card"><div class="n"><span data-c="30">0</span><em>+</em></div><div class="d">独特的问题形式化表述</div></div> <div class="card"><div class="n"><span data-c="12">0</span><em>s</em></div><div class="d">在 265k 个对象和 3.2k 个箱上的 P99 求解时间</div></div> <div class="card"><div class="n"><span data-c="171">0</span><em>s</em></div><div class="d">在 1M+ 个对象和 5k 个箱上的平均求解时间</div></div> <div class="card"><div class="n"><span data-c="3.4" data-d="1">0</span><em>k+</em></div><div class="d">在该 1M+ 对象规模下的运行次数</div></div> <div class="card"><div class="n"><span data-c="9">0</span><em>+ yrs</em></div><div class="d">开源之前在 Meta 内部的使用年限</div></div> </div> <div class="pills"> <span class="pill">Shard Manager:分片 → 服务器</span> <span class="pill">RAS:服务器 → 服务</span> <span class="pill">Taiji:边缘流量 → 数据中心</span> <span class="pill">无服务器函数分组</span> <span class="pill">ML 训练负载均衡</span> <span class="pill">会议 → 会议室</span> </div> </div> <div class="ft"> <span>来源:<a href="https://engineering.fb.com/2026/09/21/open-source/rebalancer-generic-high-performance-library-assignment-problems/" target="_blank" rel="noopener">Engineering at Meta</a> · <a href="https://github.com/facebook/rebalancer" target="_blank" rel="noopener">GitHub</a> · 标签页 1 至 3 为简化模拟</span> <span class="brand">由 Marktechpost 构建</span> </div> </div> <script> (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<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<NS;s++){v+=Math.max(0,u[s]-CAP);o+=u[s]*u[s];}return [v,o];} function better(x,y){return x[0]<y[0]||(x[0]===y[0]&&x[1]<y[1]);} function bestMove(){ var cur=score(asg),best=null,bs=cur,n=0; for(var i=0;i<asg.length;i++){for(var s=0;s<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<asg.length;i2++)for(var j=i2+1;j<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)+' 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<2;r++){h+='<div class="rack"><div class="rl">机架 '+(r?'B':'A')+'(作用域)</div><div class="srvs">'; for(var s=r*2;s<r*2+2;s++){var over=u[s]>CAP; h+='<div class="srv'+(over?' over':'')+'"><div class="sn">S'+(s+1)+'<span>'+u[s]+' / '+CAP+' CPU</span></div><div class="bar"><i style="width:'+Math.min(100,u[s]/24*100)+'%"></i><u style="left:'+(CAP/24*100)+'%"></u></div><div class="chips">'; for(var i=0;i<asg.length;i++)if(asg[i]===s){h+='<div class="chip'+(ids&&ids.indexOf(i)>-1?' hot':'')+'" data-id="'+i+'" style="width:'+(30+SIZES[i]*8)+'px">t'+(i+1)+'·'+SIZES[i]+'</div>';} h+='</div></div>';} h+='</div></div>';} 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='<line x1="0" x2="800" y1="'+ideal+'" y2="'+ideal+'" stroke="#2BD99F" stroke-dasharray="4 4" opacity=".6"/><polyline points="'+p+'" fill="none" stroke="#38D6FF" stroke-width="2.5"/>'+pts.map(function(v,i){return '<circle cx="'+(i/n*790+5)+'" cy="'+(65-(v-mn)/(mx-mn)*58)+'" r="3.5" fill="#0866FF" stroke="#fff" stroke-width="1"/>';}).join(''); } var logEl=document.getElementById('mtpLog'); function doStep(){ var r=bestMove();evals+=r.n; if(!r.best){stop();logEl.textContent='已达到局部最优:没有任何移动或交换能提升分数(共 '+evals+' 次评估)。';render();return false;} var old=asg;asg=r.best.a;step++;hist.push(score(asg)[1]); var moved=r.best.ids;logEl.textContent='第 '+step+' 步:'+r.best.txt+'(检查了 '+r.n+' 个候选)'; render(moved);drawGraph(diff(old,asg));return true; } function diff(a,b){var s={};for(var i=0;i<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=' 运行局部搜索';} document.getElementById('mtpRun').onclick=function(){if(timer){stop();return;}this.textContent='❚❚ 暂停';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(),'服务器 S1 起始状态为 4 CPU 超载。请按运行。');}; document.getElementById('mtpShuf').onclick=function(){var a=[];for(var i=0;i<12;i++)a.push(Math.random()<.55?Math.floor(Math.random()*2):Math.floor(Math.random()*4));resetTo(a,'新的随机起始状态。请按运行。');}; /* 表达式图 */ 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)>-1;} var any=live.length>0; for(var s=0;s<4;s++){ h+='<path class="edge'+(on(s)?' live':'')+'" d="M'+LX[s]+',232 L'+LX[s]+',172"/>'; h+='<path class="edge'+(on(s)?' live':'')+'" d="M'+LX[s]+',138 C'+LX[s]+',100 300,110 300,78"/>'; h+='<path class="edge'+(on(s)?' live':'')+'" d="M'+(LX[s]+40)+',233 C'+(LX[s]+60)+',200 650,130 650,78"/>'; } h+='<path class="edge'+(any?' live':'')+'" d="M300,44 L300,22"/><path class="edge'+(any?' live':'')+'" d="M650,44 L650,22"/>'; function node(x,y,w,label,val,l){return '<g class="node'+(l?' live':'')+'"><rect x="'+(x-w/2)+'" y="'+(y-17)+'" width="'+w+'" height="34" rx="8"/><text x="'+x+'" y="'+(y-2)+'">'+label+'</text><text class="val" x="'+x+'" y="'+(y+11)+'">'+val+'</text></g>';} for(var k=0;k<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+='<text x="300" y="14" fill="#4C9BFF" font-size="11" font-weight="700" text-anchor="middle">BalanceSpec 目标</text>'; h+='<text x="650" y="14" fill="'+(Math.max.apply(null,u)>CAP?'#FF5A6E':'#2BD99F')+'" font-size="11" font-weight="700" text-anchor="middle">CapacitySpec:MAX ≤ '+CAP+(Math.max.apply(null,u)>CAP?'(已违反)':'(正常)')+'</text>'; h+='<text x="410" y="292" fill="#93A3BF" font-size="11" text-anchor="middle">叶子节点:每台服务器的利用率 · 本次移动重算的节点:'+(any?(live.length*2+2):0)+' / 10</text>'; 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='已达到局部最优。请先尝试一次随机移动。';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>=1e9)return (n/1e9).toFixed(n>=1e10?0:1)+'B';if(n>=1e6)return (n/1e6).toFixed(n>=1e7?0:1)+'M';if(n>=1e3)return (n/1e3).toFixed(n>=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<=1e6){t='<b>最优(MIP)求解器是一个很好的起点。</b> 模型很小:把它交给 HiGHS、Gurobi 或 FICO Xpress,就能得到可证明最优的分配。';v.style.borderColor='#2BD99F';} else if(m<=1e8){t='<b>先用 MIP 做原型,然后转向局部搜索。</b> Meta 表示这是一条常见路径:先用最优求解器找到一个强基线,再进行迁移。';v.style.borderColor='#4C9BFF';} else{t='<b>局部搜索。</b> 该 MIP 将需要 ≈ '+fmt(m)+' 个二元变量,处于 " 最坏情况,而每个局部搜索邻域保持在 '+fmt(l)+' 附近。Meta 几乎所有大规模问题都以这种方式运行。';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<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); })(); </script> </body></html>">Rebalancer 与最接近的开源替代方案对比
| 特性 | Meta Rebalancer | Google OR-Tools | Timefold Solver(社区版) |
|---|---|---|---|
| 许可证 | Apache 2.0 | Apache 2.0 | Apache 2.0 (企业版为商业授权) |
| 核心语言 | C++ | C++ | Java |
| API | C++、Python | C++、Python、Java、C# | Java、Kotlin |
| 定位 | 通用分配问题(对象分配到箱) | 广泛套件:CP-SAT、LP、MIP 封装、路径规划、装箱、分配 | 规划:路径规划、排班、调度、任务分配 |
| 局部搜索 | 是,基于表达式图并行执行 | 是,在路径规划求解器中 (引导式局部搜索、模拟退火、禁忌搜索) | 是,核心引擎 (禁忌搜索、模拟退火、迟到接受) |
| MIP 后端 | FICO Xpress、Gurobi、HiGHS | 封装了商业与开源 MIP 求解器 | 未使用 |
| 调试 UI | 再平衡器浏览器 (Docker) | 未在 README 中列出 | Benchmarker;商业版中提供分数分析功能 |
| 安装 | pip install rebalancer | pip install ortools | Maven,JDK 21+ |
OR-Tools 覆盖更多问题类别,而 Timefold 面向 JVM 的调度和路径规划。Rebalancer 的优势在于一个分配规范即可同时在局部搜索和 MIP 上运行。
关键要点
- Rebalancer 将任何分配问题建模为对象、箱、约束和目标。
- 规范被编译为表达式图,由局部搜索或 MIP 求解器求解。
- MIP 后端包括 FICO Xpress、Gurobi 和开源的 HiGHS。
- Meta 每天运行约 40M 个问题;在 265k 个对象和 3.2k 个箱上 P99 为 12 秒。
- Apache 2.0 许可证,提供 C++ 和 Python API,现已可从 PyPI 安装。
请查看 论文, GitHub 仓库 以及 技术细节。所有功劳归于该项目的研究者。另外,欢迎在 Twitter 上关注我们,别忘了加入我们的 150k+ML SubReddit 并订阅 我们的新闻通讯。等等!你在用 telegram 吗? 现在你也可以在 telegram 上加入我们了。
本文 Meta AI 开源 Rebalancer:一个每天运行约 40 Million 个放置问题的 C++ 分配求解器 首发于 MarkTechPost.