2026年华数杯全国大学生数学建模

C题 面向算电协同的多目标调度优化研究

原题再现:

  随着人工智能算力规模的持续扩张,数据中心对电力系统的影响不断增强,电力成本已逐渐成为数据中心运营成本的核心组成部分。在此背景下,“算电协同”逐渐从政策倡导演变为产业发展的现实需求。2026年政府工作报告首次将“算电协同”纳入国家级新基建工程,明确提出“实施超大规模智算集群、算电协同等新基建工程,加强全国一体化算力监测调度,支持公共云发展”。
  算电协同更加关注算力负荷与能源系统之间的实时耦合关系,西部地区具有丰富的风能、太阳能和水电资源,电价相对较低,具备建设绿色低碳数据中心的天然优势,而东部地区则更加接近用户侧,具有更低的网络时延与更高的实时业务需求。然而,在实际运行过程中,算力跨区域迁移涉及计算任务、电力系统、新能源、储能系统和通信网络之间复杂耦合关系的多目标协同优化问题。例如,将AI任务迁移至新能源资源丰富地区虽然可以降低电费成本和碳排放,但可能导致网络时延增加;实时推理任务对时延极为敏感,而训练任务则更关注能源成本与算力规模;新能源出力具有波动性,需要储能系统进行协调。因此,如何实现AI工作负载、能源系统与通信网络的联合优化,成为当前面向算电协同的重要研究问题。
  本研究设定存在六个典型区域,RegionA、RegionB 与 RegionC 代表东部高负荷区域;RegionD 代表西部算力中心;RegionE、RegionF 分别为光伏和风电资源富集区域。任务到达主时域为第0–2399小时,粒度为1小时。第 2400–2405小时用于结清末端任务;第 2406 小时不产生新任务,也不安排未完成任务,仅用于电力与储能终端状态结算。任务包括实时推理、批量推理和AI训练三类;实时推理任务到达即开工;批量推理和 AI 训练任务允许延后,但均须在时点2406 完成,即不得占用第 2406 小时。任务不可抢占、不可拆分或中途迁移。
  请参赛者根据附件数据回答以下问题。
  问题1:基于附件中的AI 工作负载数据,对不同区域、不同类型任务的 GPU需求进行统计分析,并构建短期预测模型。请建立基础算力调度模型,并给出第2376–2399 小时实际到达任务的调度方案;跨越第2399小时的任务须在收尾时域内结清,并展示最后24小时的调度甘特图和区域GPU利用率。
  问题2:以第 0–2399 小时实际到达任务及对应逐时电力参数为输入,考虑区域电价、碳强度和可用新能源出力,以任务迁移与开工时段为决策变量(迁移后的设施负荷按统一功率映射与区域 PUE 计算),以运行成本、碳排放、网络时延和新能源利用率为目标或评价指标,建立碳感知任务调度模型,并给出调度策略。
  问题3:在给定任务调度和逐时负荷的基础上,建立储能协同优化模型,设计储能充放电策略,分析储能系统对运行成本、碳排放、区域峰值净购电功率和负荷波动程度的影响。
  问题4:在前三问基础上,建立多区域算—储—电协同优化模型,统一考虑任务迁移、电价、碳排放、可用新能源、储能、区域购售电边界、网络时延及任务异构性。请在系统运行成本、碳排放、网络时延、服务质量、新能源利用率和区域峰值净购电之间进行权衡,并比较不同碳约束、电价机制和新能源波动场景下的策略变化。

整体求解过程概述(摘要)

  针对问题一,为了刻画六个区域、三类 AI 任务的短期算力需求波动,首先依据附件字段字典对 50000 条任务轨迹进行模式核验、单位统一、时间轴对齐和 GPU-hour 特征构造,并严格采用“0–2351 小时训练、2352–2375 小时调参验证、0–2375 小时重训、2376–2399 小时最终测试”的时序划分,避免信息泄漏。在季节朴素模型与岭回归自回归模型之间进行对比后,Ridge-ARX 在验证集上的 RMSE 为 186.513 GPU-hour,在最终测试集上的 RMSE 为 224.790 GPU-hour、WAPE 为 77.144%,均优于 24 h 与 168 h 季节朴素基准。随后引入任务—区域—起始时段三维分配变量,构建满足非抢占、实时任务到达即开工、GPU 容量、IT 功率、设施功率、网络时延与最晚完成时限的基础算力调度模型。对第 2376–2399 小时实际到达的 538 个任务求解后,全部任务均成功结清,最晚完成时刻为 2405.6 h,约束违反数为 0;RegionE、RegionF 峰值 GPU 利用率分别为 98.19% 和 98.36%,说明收尾阶段资源利用紧凑但仍保持可行裕度。
  针对问题二,鉴于问题一已经完成任务数据清洗、时间重叠系数计算与资源可行域构造,后续不再重复预处理,而是在同一调度骨架上进一步引入逐时电价、碳强度、可用新能源、PUE 与网络时延,构建“运行成本—碳排放—网络时延—新能源利用率”多目标碳感知模型。为分析多目标权衡,设计 LocalFirst、Balanced 与 GreenMigration 三种代表性权重策略。结果表明,在本附件数据下 LocalFirst 的净购电成本仅 8215.45 元、碳排放 8.851 tCO₂、平均网络时延 5 ms、迁移率为 0,且新能源利用率为 56.40%;相比之下,过度向低电价低碳区域集中迁移反而造成局部负荷拥塞,使购电与碳排增加。由此可得,本题数据支持“分布式就地消纳优先、跨区迁移作为边际调节”的策略,而非简单的‘算力西迁越多越好’。
  针对问题三,依据题目规定固定采用 region_time_data.xlsx 中 Baseline_AI_IT_Load_MW 与 NonAI_IT_Load_MW,不重新优化任务迁移与开工时段。在此基础上建立储能充放电—购售电—新能源分配线性规划模型,并严格纳入 SOC 递推、SOC 上下限、充放电功率、效率、区域购售电边界及终端 SOC 不低于初始 SOC 等约束。考虑到需要在 2407 小时全时域及多场景中稳定复现,数值主方案采用与 LP 可行域一致的新能源优先滚动近似调度,并保留精确 LP 求解函数进行抽样核验。相对于附件基准运行状态,滚动方案的净运行电费由 18.0166 亿元下降至 −4.1952 亿元(负值表示售电收益超过购电支出),碳排放由 204.54 万 tCO₂ 降至 0,六区域正向净购电峰值和由 2021.26 MW 降至 0,新能源利用率由 32.86% 提升至 67.92%,负荷波动度下降约 80.78%。
  针对问题四,在前三问的基础上进一步考虑任务迁移、储能、电价、碳排放、新能源、购售电边界、网络时延和任务异构性,构建算—储—电分层协同优化模型:外层通过滚动时域任务调度确定 AI 负荷,内层通过储能—电力平衡模型完成能源侧调节,并以 ε-约束与多场景分析刻画成本、碳排、时延、服务质量、新能源利用率和区域峰值净购电之间的矛盾。名义新能源场景下,协同方案实现净运行电费 −4.1959 亿元、碳排放 0、正向峰值 0 MW;当新能源可用比例降至 70% 时,LocalFirst、Balanced、GreenMigration 分别形成“低成本低时延”“中等碳约束折中”“强碳约束”三类 Pareto 候选。若碳上限为 3.2 万 tCO₂,则 Balanced 以 30378.66 tCO₂ 满足约束并优于更激进的迁移方案;若新能源进一步下降至 55%,系统净电费转正为 1.5412 亿元、碳排升至 23.889 万 tCO₂,说明新能源波动是系统性能最关键的不确定性来源。
  综上,本文形成了“数据理解与探索 → 数据预处理与特征工程 → 短期预测 → 任务调度 → 能源与储能优化 → 多目标情景权衡 → 约束验证与鲁棒性分析”的统一建模链条。模型的核心特点是:用小时重叠系数保持非整数任务时长的能量守恒,用共享可行域避免四问重复建模,用对照策略揭示跨区迁移的非单调收益,并通过精确 LP 与快速滚动代理的分层组合兼顾理论严谨性和 50000 任务、2407 时段大规模计算的可复现性。

模型假设:

  (1)附件中各工作簿字段含义、单位和约束口径可信;不存在未记录的设备停机、网络故障与临时算力扩容。
  (2)任务不可抢占、不可拆分、不可在执行过程中迁移。任务起始决策以整小时为候选时点,但持续时间保持分钟级精度,并通过小时重叠系数折算每小时 GPU-hour 与平均 IT 功率。
  (3)实时推理任务到达即开工;所有任务均必须满足网络时延上限与最晚完成时间,任何任务不得占用第 2406 小时。
  (4)网络侧只采用附件给出的单向时延矩阵,不建立带宽、迁移数据量、传输能耗与传输费用模型。
  (5)三类任务每等效 GPU 的平均 IT 功率采用 power_mapping.xlsx 中固定映射,区域 PUE 在建模时域内视为已知参数。
  (6)新能源优先用于本地负荷,其次可用于储能充电;具有外送能力的区域可在边界内售电,剩余新能源视为弃风弃光。
  (7)储能效率、容量和充放电功率在时域内保持不变;不计电池寿命衰减成本。为避免同时充放电,滚动策略在每一时段只允许一个方向动作,精确 LP 中通过微小吞吐惩罚抑制无意义循环。

问题分析:

  问题一分析
  针对六区域多类型 AI 短时算力预测与基础算力调度综合问题,本题融合时序回归建模与带多硬约束非抢占并行机调度,分为需求预测、任务分配两大子模块。数据难点为 5 万条任务时长跨小时、负荷时序波动剧烈、区域与任务类型负荷异质性强,若直接全局聚合会掩盖分区域调度瓶颈;建模先完成统一时间尺度转换,构造日、周周期滞后特征,采用岭回归 ARX 模型抑制多重共线性,严格划分训练 / 验证 / 测试集杜绝信息泄露,以季节朴素模型做对照验证预测精度;调度层面引入小时重叠系数精准核算跨时段 GPU 占用,构建含 GPU 容量、IT 设施功率、网络时延、最晚完工时限的 0-1 整数规划模型,采用滚动候选剪枝算法降低大规模任务求解复杂度,对末端 24 小时实际任务完成全可行调度,输出区域 GPU 利用率、任务甘特图验证资源约束无越界,为后续碳感知、储能协同建模提供标准化负荷输入基底。
  问题二分析
  基于问题一完整数据预处理与调度可行域框架,本题为兼顾运行成本、碳排放、网络时延、新能源利用率的多目标跨区算力调度优化问题,新增分时电价、区域碳强度、新能源出力三类能源时序约束。核心矛盾是低价低碳西部区域集中迁移易造成局部算力拥塞、东部本地新能源闲置,多目标量纲差异大无法直接加权求和;建模统一复用任务重叠系数与负荷映射公式,构造三类差异化权重调度策略(本地优先、均衡折中、绿色迁移),分别量化迁移率、时延、电费、碳排指标,通过热力与时序图分析区域能源梯度规律,对比三类方案 Pareto 权衡关系,得出新能源充裕场景下本地分散消纳优于大规模跨区迁移的核心结论,所有调度方案均核验实时任务零等待、全部任务按期完工等服务质量约束。
  问题三分析
  本题独立于前两问任务调度模块,固定附件基准 AI 与非 AI 负荷仅优化六区域储能充放电、购售电协同策略,属于带时段耦合状态变量的线性规划优化问题。难点是储能 SOC 跨时域递推耦合,充放电功率、容量、终端储能保有量多重约束相互牵制,新能源、负荷逐时动态波动;建模搭建全时域能量守恒方程组,区分新能源自用、储能充电、电网购电 / 外送四条能量流向,设计精确线性规划求解器与新能源优先滚动代理两套求解方案,以附件基准工况为对照,量化储能削峰、消纳新能源、降低购电成本、削减碳排放四大收益,绘制 SOC 时序轨迹验证储能运行物理可行性,通过单区域抽样对比两套算法误差,兼顾大规模多场景快速测算与单工况全局最优双重需求。
  问题四分析
  本题整合问题二跨区任务调度与问题三储能电力优化,构建外层算力分配、内层能源调节的双层分层协同模型,解决 5 万任务 + 2407 小时联合优化组合爆炸难题。核心难点是新能源供给、碳政策、分时电价存在多重不确定性,成本、碳排、网络时延、电网峰值、新能源利用率多目标相互冲突;建模采用 ε- 约束法刻画多目标 Pareto 前沿,设置新能源衰减、多电价、阶梯碳配额多类压力测试场景,对比三类迁移策略在不同供给工况下的指标变化,识别新能源充裕 / 稀缺两套差异化最优调度逻辑,通过参数敏感性分析明确新能源出力是系统性能核心影响因子,完整校验全场景下 GPU 资源、网络时延、储能 SOC、电网购售电全部硬约束,形成适配不同能源政策、新能源禀赋的算电协同完整调度体系。

模型的建立与求解整体论文缩略图

在这里插入图片描述

全部论文请见下方“ 只会建模 QQ名片” 点击QQ名片即可

部分程序代码:

import os, re, zipfile, xml.etree.ElementTree as ET, math, json, csv, argparse, statistics, random
from io import BytesIO
from pathlib import Path
import numpy as np
from scipy.optimize import linprog

NS='{http://schemas.openxmlformats.org/spreadsheetml/2006/main}'
RNS='{http://schemas.openxmlformats.org/officeDocument/2006/relationships}'
REGIONS=['RegionA','RegionB','RegionC','RegionD','RegionE','RegionF']
TASK_TYPES=['RealTimeInference','BatchInference','AITraining']
RINDEX={r:i for i,r in enumerate(REGIONS)}
TINDEX={t:i for i,t in enumerate(TASK_TYPES)}


def _colidx(ref):
    m=re.match(r'([A-Z]+)',ref); letters=m.group(1); n=0
    for ch in letters: n=n*26+ord(ch)-64
    return n-1

def read_xlsx(path, sheet_name=None):
    """Dependency-free xlsx reader for value cells; enough for contest attachments."""
    with zipfile.ZipFile(path) as z:
        shared=[]
        if 'xl/sharedStrings.xml' in z.namelist():
            root=ET.fromstring(z.read('xl/sharedStrings.xml'))
            for si in root.findall(NS+'si'):
                shared.append(''.join(t.text or '' for t in si.iter(NS+'t')))
        wb=ET.fromstring(z.read('xl/workbook.xml'))
        relroot=ET.fromstring(z.read('xl/_rels/workbook.xml.rels'))
        rels={e.attrib['Id']:e.attrib['Target'] for e in relroot}
        targets=[]
        for sh in wb.find(NS+'sheets'):
            rid=sh.attrib[RNS+'id']; target=rels[rid]
            if target.startswith('/'): target=target.lstrip('/')
            elif not target.startswith('xl/'): target='xl/'+target
            targets.append((sh.attrib['name'],target))
        if sheet_name is None:
            _, target=targets[0]
        else:
            _, target=next(x for x in targets if x[0]==sheet_name)
        root=ET.fromstring(z.read(target))
        raw=[]; width=0
        for row in root.findall('.//'+NS+'sheetData/'+NS+'row'):
            vals={}
            for c in row.findall(NS+'c'):
                j=_colidx(c.attrib['r']); width=max(width,j+1)
                t=c.attrib.get('t'); v=c.find(NS+'v')
                val='' if v is None else v.text
                if t=='s' and val!='': val=shared[int(val)]
                elif t=='inlineStr':
                    isel=c.find(NS+'is'); val=''.join(x.text or '' for x in isel.iter(NS+'t')) if isel is not None else ''
                vals[j]=val
            raw.append(vals)
        headers=['']*width
        for j,v in raw[0].items(): headers[j]=v
        out=[]
        for vals in raw[1:]:
            d={}
            for j,h in enumerate(headers):
                if h: d[h]=vals.get(j,'')
            out.append(d)
        return out

def fnum(x, default=0.0):
    try: return float(x)
    except: return default

def prepare_data(data_dir):
    paths={
        'gpu':os.path.join(data_dir,'GPU_information.xlsx'),
        'lat':os.path.join(data_dir,'network_latency.xlsx'),
        'power':os.path.join(data_dir,'power_mapping.xlsx'),
        'storage':os.path.join(data_dir,'storage_information.xlsx'),
        'tasks':os.path.join(data_dir,'workload_trace.xlsx'),
        'rt':os.path.join(data_dir,'region_time_data.xlsx')}
    for p in paths.values():
        if not os.path.exists(p): raise FileNotFoundError(p)
    gpu=read_xlsx(paths['gpu'],'GPU中心基础情况')
    lat=read_xlsx(paths['lat'],'network_latency')
    power=read_xlsx(paths['power'],'任务功率映射')
    storage=read_xlsx(paths['storage'],'storage_information')
    tasks=read_xlsx(paths['tasks'],'Sheet1')
    rt=read_xlsx(paths['rt'],'region_time_data')
    g={d['Region']:{k:fnum(v) if k not in ['Region','RegionRole','CapacityLevel','Remarks'] else v for k,v in d.items()} for d in gpu}
    lmat=np.full((6,6),999.,float)
    for d in lat:lmat[RINDEX[d['FromRegion']],RINDEX[d['ToRegion']]]=fnum(d['NetworkLatency_ms'])
    pmap={d['TaskType']:fnum(d['GPU_Power_MW_per_EquivalentGPU']) for d in power}
    sto={d['Region']:{k:fnum(v) if k not in ['Region','Remarks','SOC_State_Convention'] else v for k,v in d.items()} for d in storage}
    T=[]
    for d in tasks:
        T.append({
            'TaskID':int(fnum(d['TaskID'])),'TaskType':d['TaskType'],'ArrivalHour':int(fnum(d['ArrivalHour'])),
            'GPU_Demand':fnum(d['GPU_Demand']),'Duration_h':fnum(d['EstimatedDuration_min'])/60.0,
            'Duration_min':fnum(d['EstimatedDuration_min']),'DelaySensitivity':d['DelaySensitivity'],
            'SourceRegion':d['SourceRegion'],'MaxLatency_ms':fnum(d['MaxLatency_ms']),
            'LatestFinishHour':int(fnum(d['LatestFinishHour'])),'EarliestStartHour':int(fnum(d['EarliestStartHour']))})
    H=2407
    fields=['ElectricityPrice_CNY_per_MWh','SellPrice_CNY_per_MWh','CarbonIntensity_tCO2_per_MWh','AvailableRenewable_MW','Baseline_AI_IT_Load_MW','NonAI_IT_Load_MW','GridPurchase_MW','GridSell_MW','NetGridImport_MW','CarbonEmission_tCO2','SOC_MWh','ChargePower_MW','DischargePower_MW','Total_Load_MW','IT_Load_MW','UsedRenewable_MW','RenewableCharge_MW','Curtailment_MW']
    arr={k:np.zeros((6,H),float) for k in fields}
    price_period=np.empty((6,H),object); data_period=np.empty((6,H),object)
    for d in rt:
        h=int(fnum(d['Hour'])); ri=RINDEX[d['Region']]
        for k in fields: arr[k][ri,h]=fnum(d.get(k,''))
        price_period[ri,h]=d.get('PricePeriod',''); data_period[ri,h]=d.get('DataPeriod','')
    return {'gpu':g,'lat':lmat,'power':pmap,'storage':sto,'tasks':T,'rt':arr,'price_period':price_period,'data_period':data_period}


def overlaps(start,dur,H=2407):
    end=start+dur; h0=int(math.floor(start)); h1=min(H-1,int(math.ceil(end)-1))
    out=[]
    for h in range(h0,h1+1):
        ov=max(0.,min(end,h+1)-max(start,h))
        if ov>1e-12: out.append((h,ov))
    return out

# ---------- Problem 1: forecasting ----------
def aggregate_workload(tasks):
    y=np.zeros((6,3,2400),float); cnt=np.zeros((6,3,2400),int)
    for d in tasks:
        h=d['ArrivalHour']; r=RINDEX[d['SourceRegion']]; t=TINDEX[d['TaskType']]
        y[r,t,h]+=d['GPU_Demand']*d['Duration_h']
        cnt[r,t,h]+=1
    return y,cnt

def _features(series,t):
    # autoregressive + calendar features
    lags=[1,2,3,6,12,24,48,168]
    xs=[series[t-L] for L in lags]
    xs += [np.mean(series[t-24:t]), np.mean(series[t-168:t])]
    hh=t%24; ww=t%168
    xs += [math.sin(2*math.pi*hh/24),math.cos(2*math.pi*hh/24),math.sin(2*math.pi*ww/168),math.cos(2*math.pi*ww/168)]
    return np.array(xs,float)

def ridge_fit_predict(series, train_end, pred_start, pred_end, lam=10.0, recursive=False):
    # training y indices 168..train_end inclusive
    X=[]; Y=[]
    for t in range(168,train_end+1): X.append(_features(series,t)); Y.append(series[t])
    X=np.asarray(X); Y=np.asarray(Y); mu=X.mean(0); sd=X.std(0); sd[sd<1e-8]=1
    Z=(X-mu)/sd; A=Z.T@Z+lam*np.eye(Z.shape[1]); b=Z.T@(Y-Y.mean())
    beta=np.linalg.solve(A,b); intercept=Y.mean()
    work=series.copy(); preds=[]
    for t in range(pred_start,pred_end+1):
        z=(_features(work,t)-mu)/sd; pr=max(0.,intercept+z@beta); preds.append(pr)
        if recursive: work[t]=pr
    return np.asarray(preds)

def forecast_analysis(tasks):
    y,_=aggregate_workload(tasks)
    actual_val=[]; pred24=[]; pred168=[]; predr=[]
    for r in range(6):
        for k in range(3):
            s=y[r,k]
            actual_val.extend(s[2352:2376]); pred24.extend(s[2328:2352]); pred168.extend(s[2184:2208])
            predr.extend(ridge_fit_predict(s,2351,2352,2375,lam=10.0,recursive=False))
    def metrics(a,p):
        a=np.asarray(a); p=np.asarray(p); e=p-a
        mae=float(np.mean(abs(e))); rmse=float(np.sqrt(np.mean(e*e)))
        smape=float(np.mean(2*abs(e)/(abs(a)+abs(p)+1e-6))*100)
        return {'MAE':mae,'RMSE':rmse,'sMAPE':smape}
    mets={'SeasonalNaive24':metrics(actual_val,pred24),'SeasonalNaive168':metrics(actual_val,pred168),'RidgeARX':metrics(actual_val,predr)}
    # inverse-RMSE blend weights using validation
    inv=np.array([1/mets[k]['RMSE'] for k in ['SeasonalNaive24','SeasonalNaive168','RidgeARX']]); w=inv/inv.sum()
    actual_test=[]; P24=[];P168=[];PR=[]
    for r in range(6):
        for k in range(3):
            s=y[r,k]
            actual_test.extend(s[2376:2400]); P24.extend(s[2352:2376]); P168.extend(s[2208:2232]); PR.extend(ridge_fit_predict(s,2375,2376,2399,lam=10.0,recursive=False))
    blend=w[0]*np.array(P24)+w[1]*np.array(P168)+w[2]*np.array(PR)
    mets['Blend_test']=metrics(actual_test,blend); mets['weights']={'SN24':float(w[0]),'SN168':float(w[1]),'RidgeARX':float(w[2])}
    # cube predictions for final 24
    pred_cube=np.zeros((6,3,24)); actual_cube=y[:,:,2376:2400]
    idx=0
    for r in range(6):
        for k in range(3):
            s=y[r,k]; rr=ridge_fit_predict(s,2375,2376,2399,lam=10.0,recursive=False)
            pred_cube[r,k]=w[0]*s[2352:2376]+w[1]*s[2208:2232]+w[2]*rr
    return y,mets,pred_cube,actual_cube

# ---------- Task scheduler for problems 1,2,4 ----------
def schedule_tasks(data, task_subset=None, mode='base', weights=None, renewable_scale=1.0, price_scale=None, seed=2026, initial_ai=None, allow_migration=True):
    tasks=data['tasks'] if task_subset is None else task_subset
    H=2407; g=data['gpu']; lmat=data['lat']; pmap=data['power']; rt=data['rt']
    gpu_occ=np.zeros((6,H),float); ai=np.zeros((6,H),float) if initial_ai is None else initial_ai.copy()
    nonai=rt['NonAI_IT_Load_MW']; ren=rt['AvailableRenewable_MW']*renewable_scale; price=rt['ElectricityPrice_CNY_per_MWh'].copy(); ci=rt['CarbonIntensity_tCO2_per_MWh']
    if price_scale is not None: price=price*price_scale
    avail_gpu=np.array([g[r]['Available_GPU'] for r in REGIONS]); maxit=np.array([g[r]['Max_IT_Power_MW'] for r in REGIONS]); pue=np.array([g[r]['PUE'] for r in REGIONS])
    if weights is None:
        weights={'cost':0.34,'carbon':0.30,'latency':0.12,'renew':0.14,'delay':0.10}
    # robust scaling
    pscale=max(1.,float(np.median(price[price>0]))); cscale=max(.01,float(np.median(ci[ci>0])))
    # sort: realtime first by arrival, then elastic by least slack + larger GPUh
    realtime=[d for d in tasks if d['TaskType']=='RealTimeInference']
    elastic=[d for d in tasks if d['TaskType']!='RealTimeInference']
    realtime.sort(key=lambda d:(d['ArrivalHour'],-d['GPU_Demand']))
    elastic.sort(key=lambda d:(d['LatestFinishHour']-d['ArrivalHour'], -(d['GPU_Demand']*d['Duration_h']), d['ArrivalHour']))
    ordered=realtime+elastic
    sched=[]; uns=[]
    rng=random.Random(seed)

    def feasible(d,ri,s):
        if lmat[RINDEX[d['SourceRegion']],ri]>d['MaxLatency_ms']+1e-9: return False
        if s<d['ArrivalHour'] or s+d['Duration_h']>d['LatestFinishHour']+1e-9 or s+d['Duration_h']>2406+1e-9:return False
        pw=pmap[d['TaskType']]; gpu=d['GPU_Demand']
        for h,ov in overlaps(s,d['Duration_h'],H):
            if gpu_occ[ri,h]+gpu*ov>avail_gpu[ri]+1e-7:return False
            new_ai=ai[ri,h]+gpu*pw*ov
            if nonai[ri,h]+new_ai>maxit[ri]+1e-7:return False
        return True

    def score(d,ri,s):
        src=RINDEX[d['SourceRegion']]; pw=pmap[d['TaskType']]; gpu=d['GPU_Demand']; inc_cost=inc_carbon=ren_share=0.; tot_inc=0.
        for h,ov in overlaps(s,d['Duration_h'],H):
            inc_it=gpu*pw*ov; inc_fac=pue[ri]*inc_it; existing=pue[ri]*(nonai[ri,h]+ai[ri,h])
            old_grid=max(existing-ren[ri,h],0.); new_grid=max(existing+inc_fac-ren[ri,h],0.)
            dg=new_grid-old_grid; inc_cost+=dg*price[ri,h]; inc_carbon+=dg*ci[ri,h]
            ren_share+=min(inc_fac,max(ren[ri,h]-existing,0.)); tot_inc+=inc_fac
        delay=(s-d['ArrivalHour'])/max(1.,d['LatestFinishHour']-d['ArrivalHour'])
        lat=lmat[src,ri]/max(1.,d['MaxLatency_ms'])
        if mode=='base':
            # earliest feasible / same-zone biased baseline
            migr=0 if ri==src else 1
            return 10*delay + 0.5*lat + 0.3*migr + 0.001*inc_cost/pscale
        renewable_frac=ren_share/(tot_inc+1e-9)
        return (weights['cost']*(inc_cost/(pscale*max(1.,tot_inc))) + weights['carbon']*(inc_carbon/(cscale*max(1.,tot_inc))) + weights['latency']*lat - weights['renew']*renewable_frac + weights['delay']*delay)

    # candidate starts: targeted sampling rather than exhaustive enumeration
    for n,d in enumerate(ordered):
        src=RINDEX[d['SourceRegion']]
        candidate_regions=[ri for ri in range(6) if (allow_migration or ri==src) and lmat[src,ri]<=d['MaxLatency_ms']+1e-9]
        if not candidate_regions: candidate_regions=[src]
        a=d['ArrivalHour']; latest=int(math.floor(d['LatestFinishHour']-d['Duration_h']+1e-9))
        if mode!='base' and d['TaskType']!='RealTimeInference' and len(candidate_regions)>4 and latest>=a:
            mid=min(2406,(a+latest)//2)
            def rsignal(ri):
                hs=[a,mid,latest]
                return float(np.mean([weights['cost']*price[ri,h]/pscale + weights['carbon']*ci[ri,h]/cscale - weights['renew']*min(1,ren[ri,h]/max(1,pue[ri]*nonai[ri,h])) for h in hs])) + 0.05*lmat[src,ri]/max(1,d['MaxLatency_ms'])
            keep=sorted(candidate_regions,key=rsignal)[:3]
            if src not in keep: keep.append(src)
            candidate_regions=keep
        if d['TaskType']=='RealTimeInference': starts=[a]
        else:
            if latest<a: starts=[]
            else:
                span=latest-a
                if mode=='base':
                    starts=list(range(a,min(latest,a+48)+1))
                    if latest>a+48: starts += list(range(a+54,latest+1,6))
                else:
                    # collect hourly near-term + 6h/24h grid + low signal candidates
                    st=set(range(a,min(latest,a+24)+1))
                    st.update(range(a,latest+1,12 if span<336 else 24))
                    # add deterministic points across interval
                    if span>0:
                        for q in np.linspace(a,latest,min(8,span+1)): st.add(int(round(q)))
                    starts=sorted(x for x in st if a<=x<=latest)
        best=None; bestsc=float('inf')
        for ri in candidate_regions:
            # for long candidate lists, rank approximately by exogenous signal and keep top 30 + earliest
            sset=starts
            if len(sset)>60 and mode!='base':
                sig=[]
                for s in sset:
                    h=min(s,H-1); ex=(weights['cost']*price[ri,h]/pscale+weights['carbon']*ci[ri,h]/cscale-weights['renew']*min(1,ren[ri,h]/max(1,pue[ri]*nonai[ri,h])))
                    sig.append((ex,s))
                chosen={s for _,s in sorted(sig)[:12]}; chosen.update(starts[:3]); chosen.update(starts[-2:]); sset=sorted(chosen)
            for s in sset:
                if feasible(d,ri,s):
                    sc=score(d,ri,s)
                    if sc<bestsc: bestsc=sc; best=(ri,s)
        # fallback exhaustive at coarse+hourly if needed
        if best is None and d['TaskType']!='RealTimeInference' and latest>=a:
            for ri in candidate_regions:
                for s in range(a,latest+1):
                    if feasible(d,ri,s): best=(ri,s);break
                if best:break
        if best is None:
            uns.append(d['TaskID']); continue
        ri,s=best; pw=pmap[d['TaskType']]
        for h,ov in overlaps(s,d['Duration_h'],H):
            gpu_occ[ri,h]+=d['GPU_Demand']*ov; ai[ri,h]+=d['GPU_Demand']*pw*ov
        sched.append({**d,'ExecRegion':REGIONS[ri],'StartHour':float(s),'FinishHour':float(s+d['Duration_h']),'NetworkLatency_ms':float(lmat[src,ri]),'Migrated':int(ri!=src)})
    return sched,uns,gpu_occ,ai
全部论文请见下方“ 只会建模 QQ名片” 点击QQ名片即可
Logo

DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。

更多推荐