Intenzivní výpočty v systému
Wolfram Mathematica 8.0

Zdeněk Buk

září 2011, setkání uživatelů systému Mathematica, Praha

Obsah    Prezentace

Konfigurace experimentů

Distribuce výpočtů

Dávkové zpracování

Sběr výsledků

Úvod    Intenzivní výpočty

Co si představit pod pojmem intenzivní výpočty ?

Řádově statisíce experimentů,

s celkovou dobou výpočtu v řádech dnů.

V čem je problém?

Velké množství různých experimentů (nastavení parametrů),

dlouhá doba výpočtu

⇒ prakticky nemožné realizovat v interaktivním režimu (Uživatel ↔ FrontEnd ↔ Kernel)

Jak na to?

Paralelizace → urychlení výpočtu

Dávkové zpracování → odstranění nutnosti interakce s uživatelem (programátorem)

Automatické generování úloh → co nejjednodušší konfigurace experimentů

Co všechno řešíme?

Velké množství různých experimentů (nastavení parametrů)

Konfigurace paralelního prostředí (vzdálené kernely)

Plánování, distribuce a dynamické vytváření velkého množství úloh

Sběr a vyhodnocení výsledků

Jak toto všechno technicky realizovat pro dávkové zpracování?

Experimenty    Generování

Definice základní výpočetní funkce

Funkce realizující vlastní výpočet.

experiment[id_,set_,f_,opts___?OptionQ]:=Module[
{},
(* Zpracování parametrů *)
(* Vlastní experiment / výpočet *)

Return[{id,set,ToString[f],"result" (* výsledky *)}]
]

Options[experiment]={
Method->"DifferentialEvolution",
PopulationSize->Automatic
(* Parametry experimentu *)
};

Příklady spuštění pro různá nastavení parametrů

experiment[1,1,f1,Method->"CEA",PopulationSize->50]
experiment[2,1,f1,Method->"CEA",PopulationSize->50]
experiment[3,2,f1,Method->"CEA",PopulationSize->10]
experiment[4,3,f2,Method->"CEA",PopulationSize->10]
experiment[5,4,f1,Method->"DifferentialEvolution",PopulationSize->50]
experiment[6,5,f1,Method->"DifferentialEvolution",PopulationSize->20]
experiment[7,6,f1,Method->"SNES",PopulationSize->10]
(* atd... *)

Generování parametrů

Např. parametr velikost populace (PopulationSize) může nabývat hodnot 10, 20, 50 a 100.

PopulationSize->#&/@{10,20,50,100}

pFunction={f1,f2};
pMethod=Method->#&/@
{"DifferentialEvolution","SimulatedAnnealing","SNES","CEA"};
pPopulationSize=PopulationSize->#&/@
{10,20,50,100};

pMethod

Flatten[Outer[List,pFunction,pMethod,pPopulationSize],2]//TableForm

configurations=MapIndexed[Join[#2,#1]&,Flatten[Outer[List,pFunction,pMethod,pPopulationSize],2]]

nRepeat=100;
cnt=0;
allConfigurations=Flatten[Table[Join[{cnt++},#],{nRepeat}]&/@configurations,1];
Take[allConfigurations,10]

Length[allConfigurations]

allConfigurations[[1]]

experiment[Sequence@@allConfigurations[[1]]]

Manipulate[Experiment[Sequence@@allConfigurations[[i]]],{i,1,Length[allConfigurations],1}]

Paralelizace    Příprava

Konfigurace výpočetních kernelů

LaunchKernels[]

LaunchKernels[Table["localhost",{9}]]

Distribuce funkcí a knihoven

DistributeDefinitions[experiment,f1,f2 (* ... *)]

ParallelNeeds[ (* Knihovny *) ]

Distribuce potřebných definic funkcí je extrémně důležitá, program může fungovat i bez toho, aniž by hlásil chybu, zrychlení výpočtu však bude nulové.

Příklad

LaunchKernels[{"localhost","localhost"}]

Definice funkce (zde pouzep ro ukázku jednoduchá funkce - v praxi však výpočetně náročný kód)

f[x_]:=x! (* zde je výpočetně velmi náročný kód *)

Test lokálního vyhodnocení

f[10]

Test paralelního vyhodnocení

jobs=ParallelSubmit[f[#]]&/@{10,15,20}

WaitAll[jobs]

Upravená verze testovací funkce (kromě výsledku vrací i ID kernelu, který ho vypočítal)

g[x_]:={$KernelID,x!} (* zde je výpočetně velmi náročný kód *)

g[10]

jobs=ParallelSubmit[g[#]]&/@{10,15,20}

WaitAll[jobs]

Distribuce testovací funkce

DistributeDefinitions[g]

jobs=ParallelSubmit[g[#]]&/@{10,15,20}

WaitAll[jobs]

Paralelizace    Plánování úloh

Jednoduchý přístup

ParallelSubmit[(* úloha *)]

Seznam konfigurací experimentů máme,

vytvoříme tedy velkou množinu úloh a necháme vyhodnotit...

(* Pro názornost vybereme pouze část konfigurací *)
cfgList=Take[RandomSample[allConfigurations],10]

jobs=ParallelSubmit[experiment[Sequence@@#]]&/@cfgList

WaitAll[jobs]

Jednoduchý přístup - problémy

Nemáme plnou kontrolu nad frontou

Problematický průbězný sběr výsledků (předpokládáme zápis výsledků do souboru)

Výpočetní náročnost závislá na počtu úloh, při velkém počtu úloh pozorováno značné zpomalení (už při desítkách tisíc úloh na Linuxovém kernelu)

Dynamická tvorba úloh, vlastní obsluha fronty

Potřeba vývojového balíčku (součást standardní instalace)

Needs["Parallel`Developer`"]

Základní cyklus

While[True,

	While[$QueueLength==0,
		(* Vytvoření úlohy a zařazení do fronty *)
		AppendTo[ids, ParallelSubmit[(* ... *)]];
		If[!QueueRun[], Break[]]
	];

	If[Length[ids]>0,
		{result,id,ids}=WaitNext[ids]
		(* Zpracování výsledků *)
	];
];

Dynamická tvorba úloh, vlastní obsluha fronty - podrobněji

outf=OpenWrite[fname]; (* Vytvoření výstupního souboru *)
WriteString[outf, "hlavička souboru"];
Close[outf];

d=1; ids={}; done=0; (* Pomocné proměnné *)
While[True,

	While[$QueueLength==0,
		(* Pokud nezbývají žádné volné konfigurace, končíme *)
		If[d>Length[allConfigurations],Break[]];
		(* Vybereme jednu konfiguraci *)
		cfg=allConfigurations[[d]];
		AppendTo[ids,ParallelSubmit[{cfg},experiment[Sequence@@(cfg)]]];
		d++;
		If[!QueueRun[], Break[]];
	];

	If[Length[ids]>0,
		(* Vybereme/necháme spočítat jeden výsledek *)
		{result,id,ids}=WaitNext[ids];
		done++;
		(* Otevření výstupního osuboru pro připisování *)
		outf=OpenAppend[fname];
		WriteString[outf,ExportString[{result,{}},"TSV"]];
		Close[outf];
	];

	(* Nic nezbývá -> konec *)
	If[d>=Length[allConfigurations]&&Length[ids]==0,Break[]];
];

Kód    Vytvoření a dávkové zpracování

Jak vytvořit soubory vhodné ke spuštění?

Wolfram Workbench

Mathematica notebook

Export .m souborů (inicializační buňky v notebooku).

Ukázka...

Spuštění