昇腾NPU协同调度系统

华为笔试真题 6月3号 非AI方向 300分题型

题目内容

设计一个调度系统管理昇腾 NPUNPUNPU 上的训练任务,需满足以下条件:

任务属性(每个任务包含333个维度)

  1. npusnpusnpus:所需 NPU 卡编号列表
  2. durationdurationduration:执行需要的时间片数
  3. dependondependondependon:必须在该任务前完成的任务 IDIDID(只会依赖 000 或者 111 个上游任务)
    调度规则
  4. 每张 NPUNPUNPU 卡同一时间只能执行一个任务
  5. 任务必须在前置任务完成后才能开始
  6. 时间片为正整数,多个满足条件的任务可并行启动,不可抢占已分配任务
  7. 可能存在多种调度方案,需要选出全部任务结束耗时最短的一种
    用例保证存在可调度方案(不存在任务间循环依赖),输出所有任务全部完成需要的最短时间。

输入描述

111 行,NPUNPUNPU 卡总数(1<=NPU卡总数<=101<=NPU卡总数<=101<=NPU卡总数<=10),总任务数量(1<=总任务数量<=81<=总任务数量<=81<=总任务数量<=8),如:4 2
222 行,【第 2−42-424 行第 000 个任务】,任务 000 依赖的 npunpunpu 列表(空格分割),如:000 111
333 行,【第 2−42-424 行第 000 个任务】,任务 000 持续时间,如:555
444 行,【第 2−42-424 行第 000 个任务】,任务 000 依赖的上游任务 IDIDID(无依赖时固定为 −1-11),如:111
555 行,【第 5−75-757 行第 111 个任务】,任务 111 依赖的 npunpunpu 列表(空格分割),如:111 222
666 行,【第 5−75-757 行第 111 个任务】,任务 111 持续时间,如:444
777 行,【第 5−75-757 行第 111 个任务】,任务 111 依赖的上游任务 IDIDID(无依赖时固定为 −1-11),如:000
……,如果存在更多任务,333 行一组进行输入,任务 iii 依赖的 npunpunpu 列表(空格分割),如:111 222
……,如果存在更多任务,333 行一组进行输入,任务 iii 持续时间,如:444
……,如果存在更多任务,333 行一组进行输入,任务 iii 依赖的上游任务 IDIDID(无依赖时固定为 −1-11),如:000
注意:题目保证输入合法,无需校验输入

输出描述

格式:intintint 类型数值
解释:所有任务全部完成需要的最短时间,用例保证存在可调度方案(不存在任务间循环依赖)

样例1

输入

2 3
0
3
-1
0
2
-1
1
4
1

输出

6

说明

  • 222 # 222个NPU卡,333个任务
  • 000 # 任务000需要NPU卡000
  • 333 # 任务000持续333
  • −1-11 # 任务000无依赖
  • 000 # 任务111需要NPU卡000
  • 222 # 任务111持续222
  • −1-11 # 任务111无依赖
  • 111 # 任务222需要NPU卡111
  • 444 # 任务222持续444
  • 111 # 任务222依赖任务111
    方案1:优先执行耗时长的任务(任务000先执行)
  • 时间000~333 NPU000: 任务000(持续333,时间000-333) NPU111: 空闲(任务222依赖任务111,还未完成)
  • 时间333~555 NPU000: 任务111(持续222,时间333-555) NPU111: 空闲(任务222需等待任务111,任务111在时间555完成,但任务222还未开始)
  • 时间555~999 NPU000: 空闲 NPU111: 任务222(持续444,时间555-999
    任务完成时间: 任务000: 时间333完成 任务111: 时间555完成 任务222: 时间999完成
    总时间:999
    方案2:优先执行耗时短的任务(任务111先执行)
  • 时间000~222 NPU000: 任务111(持续222,时间000-222) NPU111: 空闲(任务222依赖任务111,还未完成)
  • 时间222~666 NPU000: 任务000(持续333,时间222-555) NPU111: 任务222(持续444,时间222-666,任务111在时间222已完成,满足依赖)
  • 时间555~666 NPU000: 空闲(任务000已完成) NPU111: 任务222继续执行(时间555-666
    任务完成时间:
    任务111: 时间222完成 任务000: 时间555完成 任务222: 时间666完成
    总时间:666

样例2

输入

3 2
0 1
4
-1
1 2
5
0

输出

9

说明

  • 333 222 # 333个NPU卡,222个任务
  • 0 10\ 10 1 # 任务000需要NPU卡000、卡111
  • 444 # 任务000持续444
  • −1-11 # 任务000无依赖
  • 1 21\ 21 2 # 任务111需要NPU卡111、卡222
  • 555 # 任务111持续555
  • 000 # 任务111依赖任务000
    调度顺序: 任务111依赖任务000,只能先执行任务000再执行任务111,总耗时4+5=94+5=94+5=9,输出999
    提示 NPUNPUNPU 编号从 000 开始,任务编号从 OOO 开始

题解

思路

递归回溯实现,由于本题任务数较小1 <= M <= 8完成可以枚举所有执行情况来确定最小时间。具体逻辑如下:

  1. 接受输入,记录每个任务的依赖NPU,任务执行时长依赖任务
  2. 定义finish记录每个任务完成时间,npuAvalid记录每个NPU空闲时间,complete二进制记录每个任务完成标志。然后进行递归回溯
  3. 递归从前往后选取一个任务执行,选取任务需要满足以下条件
    • 任务还未完成
    • 任务依赖的任务已经完成
  4. 计算任务的开始时间startTime,为依赖的npu空闲时间依赖任务的最大值,任务结束时间就为startTime + duration, 更新对应finish以及依赖Npu的空闲时间,往下进行递归
  5. 递归结束条件为complete == (1 << m) -1

C++

#include<bits/stdc++.h>
using namespace std;

struct Task {
    vector<int> npus;
    int duration;
    int pre;
};


int ans = INT_MAX;
// NPU卡 // 任务数量
int m, n;
vector<Task> task;
vector<int> finish;

void dfs(int complete, vector<int>& npuAvail) {
    // 所有任务都已调度,更新最优解
    if (complete == (1 << n) - 1) {
        int finishTime = *max_element(npuAvail.begin(), npuAvail.end());
        ans = min(ans, finishTime);
        return;
    }
    // 剪枝:当前时间已超过已知最优解
    int curMax = *max_element(npuAvail.begin(), npuAvail.end());
    if (curMax >= ans) return;
    // 尝试调度一个任务
    for (int i = 0; i < n; i++) {
        // 跳过已完成的任务
        if (complete & (1 << i)) continue;
        // 检查依赖是否满足
        if (task[i].pre != -1 && !(complete & (1 << task[i].pre))) {
            continue;
        }
        // 计算任务最早开始时间
        int startTime = 0;
        for (int npu : task[i].npus) {
            startTime = max(startTime, npuAvail[npu]);
        }
        // 晚于依赖任务完成时间
        if (task[i].pre != -1) {
            startTime = max(startTime, finish[task[i].pre]);
        }
        int endTime = startTime + task[i].duration;
        // 记录NPU的原始状态,用于回溯
        vector<int> oldAvail(m);
        for (int npu : task[i].npus) {
            oldAvail[npu] = npuAvail[npu];
            npuAvail[npu] = endTime;  // 分配NPU
        }
        finish[i] = endTime;
        dfs(complete | (1 << i), npuAvail);
        finish[i] = -1;
        // 回溯:恢复NPU状态
        for (int npu : task[i].npus) {
            npuAvail[npu] = oldAvail[npu];
        }
    }
}


int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    cin >> m >> n; 
    task.resize(n);
    for (int i = 0; i < n; i++) {
        string line;
        cin.ignore();
        getline(cin, line);
        stringstream ss(line);
        int x;
        while (ss >> x) {
            task[i].npus.push_back(x);
        }
        cin >> task[i].duration;
        cin >> task[i].pre;
    }
    
    finish.resize(n, -1);
    vector<int> npuAvail(m, 0);
    dfs(0, npuAvail);


    cout << ans << "\n";
}

java

import java.io.*;
import java.util.*;

public class Main {

    static class Task {
        List<Integer> npus = new ArrayList<>();
        int duration;
        int pre;
    }

    static int ans = Integer.MAX_VALUE;

    // NPU卡数量 // 任务数量
    static int m, n;

    static List<Task> task = new ArrayList<>();
    static int[] finish;

    static void dfs(int complete, int[] npuAvail) {
        // 所有任务都已调度,更新最优解
        if (complete == (1 << n) - 1) {
            int finishTime = 0;
            for (int x : npuAvail) {
                finishTime = Math.max(finishTime, x);
            }
            ans = Math.min(ans, finishTime);
            return;
        }

        // 剪枝:当前时间已超过已知最优解
        int curMax = 0;
        for (int x : npuAvail) {
            curMax = Math.max(curMax, x);
        }
        if (curMax >= ans) return;

        // 尝试调度一个任务
        for (int i = 0; i < n; i++) {

            // 跳过已完成的任务
            if ((complete & (1 << i)) != 0) continue;

            // 检查依赖是否满足
            if (task.get(i).pre != -1 &&
                    (complete & (1 << task.get(i).pre)) == 0) {
                continue;
            }

            // 计算任务最早开始时间
            int startTime = 0;

            for (int npu : task.get(i).npus) {
                startTime = Math.max(startTime, npuAvail[npu]);
            }

            // 晚于依赖任务完成时间
            if (task.get(i).pre != -1) {
                startTime = Math.max(startTime, finish[task.get(i).pre]);
            }

            int endTime = startTime + task.get(i).duration;

            // 记录NPU原状态
            int[] oldAvail = new int[m];

            for (int npu : task.get(i).npus) {
                oldAvail[npu] = npuAvail[npu];
                npuAvail[npu] = endTime;
            }

            finish[i] = endTime;

            dfs(complete | (1 << i), npuAvail);

            finish[i] = -1;

            // 回溯
            for (int npu : task.get(i).npus) {
                npuAvail[npu] = oldAvail[npu];
            }
        }
    }

    public static void main(String[] args) throws Exception {

        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));

        String[] first = br.readLine().trim().split("\\s+");

        m = Integer.parseInt(first[0]);
        n = Integer.parseInt(first[1]);

        for (int i = 0; i < n; i++) {

            Task t = new Task();

            String[] npuLine = br.readLine().trim().split("\\s+");

            for (String s : npuLine) {
                t.npus.add(Integer.parseInt(s));
            }

            t.duration = Integer.parseInt(br.readLine().trim());
            t.pre = Integer.parseInt(br.readLine().trim());

            task.add(t);
        }

        finish = new int[n];
        Arrays.fill(finish, -1);

        int[] npuAvail = new int[m];

        dfs(0, npuAvail);

        System.out.println(ans);
    }
}

python

import sys

ans = float('inf')

# NPU卡数量 // 任务数量
m, n = map(int, input().split())

task = []

for _ in range(n):
    npus = list(map(int, input().split()))
    duration = int(input())
    pre = int(input())

    task.append({
        "npus": npus,
        "duration": duration,
        "pre": pre
    })

finish = [-1] * n


def dfs(complete, npu_avail):
    global ans

    # 所有任务都已调度,更新最优解
    if complete == (1 << n) - 1:
        finish_time = max(npu_avail)
        ans = min(ans, finish_time)
        return

    # 剪枝:当前时间已超过已知最优解
    cur_max = max(npu_avail)
    if cur_max >= ans:
        return

    # 尝试调度一个任务
    for i in range(n):

        # 跳过已完成的任务
        if complete & (1 << i):
            continue

        # 检查依赖是否满足
        if task[i]["pre"] != -1 and not (complete & (1 << task[i]["pre"])):
            continue

        # 计算任务最早开始时间
        start_time = 0

        for npu in task[i]["npus"]:
            start_time = max(start_time, npu_avail[npu])

        # 晚于依赖任务完成时间
        if task[i]["pre"] != -1:
            start_time = max(start_time, finish[task[i]["pre"]])

        end_time = start_time + task[i]["duration"]

        old_avail = {}

        for npu in task[i]["npus"]:
            old_avail[npu] = npu_avail[npu]
            npu_avail[npu] = end_time

        finish[i] = end_time

        dfs(complete | (1 << i), npu_avail)

        finish[i] = -1

        # 回溯
        for npu in task[i]["npus"]:
            npu_avail[npu] = old_avail[npu]


npu_avail = [0] * m

dfs(0, npu_avail)

print(ans)

javascript

const readline = require('readline');

const rl = readline.createInterface({
    input: process.stdin,
    output: process.stdout
});

const lines = [];

rl.on('line', line => {
    lines.push(line.trim());
});

rl.on('close', () => {

    let idx = 0;

    const [m, n] = lines[idx++].split(' ').map(Number);

    const task = [];

    for (let i = 0; i < n; i++) {

        const npus = lines[idx++].split(' ').map(Number);

        const duration = Number(lines[idx++]);

        const pre = Number(lines[idx++]);

        task.push({
            npus,
            duration,
            pre
        });
    }

    let ans = Number.MAX_SAFE_INTEGER;

    const finish = new Array(n).fill(-1);

    function dfs(complete, npuAvail) {

        // 所有任务都已调度,更新最优解
        if (complete === (1 << n) - 1) {
            const finishTime = Math.max(...npuAvail);
            ans = Math.min(ans, finishTime);
            return;
        }

        // 剪枝:当前时间已超过已知最优解
        const curMax = Math.max(...npuAvail);

        if (curMax >= ans) {
            return;
        }

        // 尝试调度一个任务
        for (let i = 0; i < n; i++) {

            // 跳过已完成的任务
            if (complete & (1 << i)) continue;

            // 检查依赖是否满足
            if (
                task[i].pre !== -1 &&
                !(complete & (1 << task[i].pre))
            ) {
                continue;
            }

            // 计算任务最早开始时间
            let startTime = 0;

            for (const npu of task[i].npus) {
                startTime = Math.max(startTime, npuAvail[npu]);
            }

            // 晚于依赖任务完成时间
            if (task[i].pre !== -1) {
                startTime = Math.max(
                    startTime,
                    finish[task[i].pre]
                );
            }

            const endTime = startTime + task[i].duration;

            const oldAvail = {};

            for (const npu of task[i].npus) {
                oldAvail[npu] = npuAvail[npu];
                npuAvail[npu] = endTime;
            }

            finish[i] = endTime;

            dfs(complete | (1 << i), npuAvail);

            finish[i] = -1;

            // 回溯
            for (const npu of task[i].npus) {
                npuAvail[npu] = oldAvail[npu];
            }
        }
    }

    const npuAvail = new Array(m).fill(0);

    dfs(0, npuAvail);

    console.log(ans);
});

Go

package main

import (
	"bufio"
	"fmt"
	"os"
	"strconv"
	"strings"
)

type Task struct {
	npus     []int
	duration int
	pre      int
}

var ans = int(^uint(0) >> 1)

// NPU卡数量 // 任务数量
var m, n int

var task []Task
var finish []int

func max(a, b int) int {
	if a > b {
		return a
	}
	return b
}

func dfs(complete int, npuAvail []int) {

	// 所有任务都已调度,更新最优解
	if complete == (1<<n)-1 {

		finishTime := 0

		for _, v := range npuAvail {
			if v > finishTime {
				finishTime = v
			}
		}

		if finishTime < ans {
			ans = finishTime
		}

		return
	}

	// 剪枝:当前时间已超过已知最优解
	curMax := 0

	for _, v := range npuAvail {
		if v > curMax {
			curMax = v
		}
	}

	if curMax >= ans {
		return
	}

	// 尝试调度一个任务
	for i := 0; i < n; i++ {

		// 跳过已完成的任务
		if complete&(1<<i) != 0 {
			continue
		}

		// 检查依赖是否满足
		if task[i].pre != -1 &&
			(complete&(1<<task[i].pre)) == 0 {
			continue
		}

		// 计算任务最早开始时间
		startTime := 0

		for _, npu := range task[i].npus {
			startTime = max(startTime, npuAvail[npu])
		}

		// 晚于依赖任务完成时间
		if task[i].pre != -1 {
			startTime = max(startTime, finish[task[i].pre])
		}

		endTime := startTime + task[i].duration

		oldAvail := make(map[int]int)

		for _, npu := range task[i].npus {
			oldAvail[npu] = npuAvail[npu]
			npuAvail[npu] = endTime
		}

		finish[i] = endTime

		dfs(complete|(1<<i), npuAvail)

		finish[i] = -1

		// 回溯
		for _, npu := range task[i].npus {
			npuAvail[npu] = oldAvail[npu]
		}
	}
}

func main() {

	reader := bufio.NewReader(os.Stdin)

	line, _ := reader.ReadString('\n')
	line = strings.TrimSpace(line)

	first := strings.Fields(line)

	m, _ = strconv.Atoi(first[0])
	n, _ = strconv.Atoi(first[1])

	task = make([]Task, n)

	for i := 0; i < n; i++ {

		line, _ = reader.ReadString('\n')
		line = strings.TrimSpace(line)

		fields := strings.Fields(line)

		var npus []int

		for _, s := range fields {
			x, _ := strconv.Atoi(s)
			npus = append(npus, x)
		}

		line, _ = reader.ReadString('\n')
		duration, _ := strconv.Atoi(strings.TrimSpace(line))

		line, _ = reader.ReadString('\n')
		pre, _ := strconv.Atoi(strings.TrimSpace(line))

		task[i] = Task{
			npus:     npus,
			duration: duration,
			pre:      pre,
		}
	}

	finish = make([]int, n)

	for i := range finish {
		finish[i] = -1
	}

	npuAvail := make([]int, m)

	dfs(0, npuAvail)

	fmt.Println(ans)
}
Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐