Memória unificada a peça final do enigma de programação gpu remissão de artrite reumatóide sem medicação

O suporte para memória unificada entre CPUs e GPUs em sistemas de computação acelerados é a peça final de um quebra-cabeças de programação que estamos montando há cerca de dez anos. A memória unificada tem um profundo impacto no gerenciamento de dados para programação paralela de GPU, particularmente nas áreas de controle de produtividade e desempenho. Desenvolvimentos recentes com CUDA Unified Memory melhoraram muito a produtividade, e por que o próximo trabalho em memória unificada real será um salto tão grande.

No SC18, em novembro, ainda ouvi comentários de que as GPUs são difíceis de programar em relação às CPUs. Eu tenho dito nos últimos dez anos ou mais que a programação paralela é difícil e sempre será. Se é um dado que qualquer programa de GPU deve ser um programa paralelo, então talvez isso torne isso difícil por definição.

Mas agora vivemos em um mundo onde qualquer programa HPC deve ser um programa paralelo.

Isso significa, por exemplo, que o uso de todas as linhas de cálculo (incluindo instruções SIMD) nos sistemas Intel Xeon SP “Skylake” de soquete duplo mais recentes requer centenas de operações paralelas por ciclo. Você vai ter que escrever um programa paralelo para usar efetivamente qualquer CPU moderna. Portanto, não é a programação paralela em si que torna a programação da GPU mais difícil do que a programação da CPU. Grande parte do esforço extra decorre do fato de que a memória do dispositivo da GPU é fisicamente separada da memória do sistema da CPU. Ao usar CUDA, ou OpenCL, ou Thrust, ou OpenACC para escrever programas de GPU, o desenvolvedor é geralmente responsável por organizar os dados para dentro e fora da memória da GPU conforme necessário para suportar a execução de kernels da GPU. Isto tem sido verdade desde o primeiro lançamento do compilador Nvidia CUDA C em 2007.

Em um sistema somente de CPU, você geralmente não pensa em gerenciar dados e não precisa fazer isso há muito tempo. O significado de artrite do sistema de CPU na memória urdu é baseado em cache, otimizado para latência e tende a ter uma capacidade muito alta – centenas de gigabytes por nó, ou até terabytes em alguns dos servidores de hoje. Antes que a memória virtual fosse inventada e se tornasse mainstream, as pessoas usavam sobreposições de memória e algoritmos fora do núcleo que eram explicitamente codificados em seus programas. Mas essas técnicas são quase desconhecidas hoje em HPC. Para a maioria dos programas, os dados são paginados na memória do sistema e permanecem lá durante a execução do programa. A partir daí, torna-se o trabalho de um ou mais caches de hardware mover e manter os dados próximos da CPU quando necessário. O gerenciamento de dados em uma CPU basicamente equivale à programação para a localidade do cache e, na maior parte, as pessoas aprenderam a programar muito bem para a localidade do cache. Isso não é visto como um problema porque a localidade funciona bem para a maioria dos programas em execução nas CPUs.

Em sistemas híbridos de computação acelerada, a memória do dispositivo GPU é muito menor – 16 GB a 32 GB nos aceleradores de GPU “Volta” – com latências mais longas e muito mais altas em relação às velocidades de memória de tratamento de artrose. A largura de banda da memória do dispositivo da GPU é normalmente 5X a 10X superior à da CPU DRAM, e a latência mais longa não importa em um mecanismo de computação de GPU otimizado por fluxo arquitetado para tolerar latências por meio de multithreading baseado em hardware muito eficiente. Na maioria dos sistemas acelerados, essas duas memórias são conectadas usando um barramento PCI-Express, que é um barramento de E / S muito bom, mas não um barramento de memória muito bom. Na melhor das hipóteses, o PCI-Express pode fornecer algumas dezenas de gigabytes por segundo ao mover dados entre as duas memórias. Isso o torna um gargalo e o modelo de programação CUDA foi projetado para concentrar a programação no gerenciamento explícito de dados, a fim de maximizar a eficiência e o desempenho.

Quando o PGI Accelerator original e as diretivas OpenACC posteriores surgiram, eles abstraíram muitos dos aspectos específicos da GPU da programação do acelerador: sem mais extensões de linguagem, sem necessidade de criar loops em funções do lado do dispositivo, não há necessidade de explicitamente empacotar argumentos para kernels de dispositivos, e não há necessidade de transportar duas versões do código-fonte para manter os problemas de artrite na portabilidade dos dedos para outros compiladores e sistemas. CUDA Fortran foi definido de uma maneira que permitia aos programadores colocar dados em vários tipos de memória usando atributos de variáveis ​​e mover dados entre memórias com instruções de atribuição de matriz. Da mesma forma, o Thrust criou um namespace de C ++ e um modelo de programação GPU, incluindo gerenciamento de dados que é muito confortável para qualquer pessoa familiarizada com o uso de bibliotecas de classes C ++. Em todos esses modelos, no entanto, enquanto o problema de gerenciamento de dados é abstraído no modelo de programação de uma maneira que o torna mais fácil ou mais natural, ainda é necessário que o programador esteja ciente e otimize a movimentação de dados entre a memória do sistema da CPU e Memória do dispositivo da GPU. Leva tempo e esforço do programador para entender os requisitos de movimentação de dados, para adicionar código de gerenciamento de dados em qualquer modelo de programação que você esteja usando e para otimizá-lo para minimizar o afunilamento de PCIe.

Para maximizar o desempenho em uma GPU, você move os dados para a memória do dispositivo antes de serem necessários e, em seguida, move-os de volta antes que sejam necessários novamente no host. Sempre que possível, você tenta sobrepor o movimento de dados com a execução de outros kernels computacionais para ocultar a sobrecarga de movimentação de dados no perfil de execução. Mas o fato é que, em muitos casos, os dados permanecem na memória da GPU por um bom tempo. Frequentemente, as mesmas grandes estruturas de dados são usadas por longas seqüências de kernels, com a CPU em um loop lançando núcleos de computação na GPU que operam em dados que são, na maior parte, osteoartrite residentes em hindi na memória do dispositivo da GPU. Soa como um cache para mim, ou talvez mais como memória virtual, onde os dados são todos paginados e, em seguida, permanece na maior parte residente durante a execução do programa.

O pessoal da Nvidia que desenvolve a CUDA reconheceu isso há muito tempo e, em 2014, introduziu um recurso chamado CUDA Unified Memory. Em vez de alocar a memória do dispositivo com o cudaMalloc, agora você poderia alocá-lo com uma nova chamada cudaMallocManaged que alocaria um único ponteiro acessível pela GPU ou pela CPU. Usando alguma mágica de nível de sistema no driver de dispositivo CUDA, os dados alocados dessa maneira são paginados para frente e para trás entre a memória do sistema da CPU e a memória do dispositivo da GPU mais ou menos sob demanda. Não é estritamente pedido por paginação, porque às vezes o gerente da Unified Memory decide que não vale a pena mover os dados em uma direção ou outra, mas a ideia básica é a mesma. Os dados migram automaticamente para a memória do processador que o está usando com mais frequência – não é necessária intervenção do programador. O efeito imediato foi simplificar o desenvolvimento de muitos programas CUDA. Qualquer programa que use principalmente dados alocáveis ​​na GPU torna-se muito mais fácil de escrever, sem a necessidade de mover esses dados com chamadas da API cudaMemcpy em CUDA C / C ++ ou atribuições de matriz no CUDA Fortran.

Quando eu ouvi falar sobre isso pela primeira vez, eu pensei que era essencialmente “muito tarde”, paginação de demanda para a GPU. Mas a localidade trabalha a nosso favor, e os programas apropriados para a osteoartrite da computação de GPUs icd 10 tendem a ter uma intensidade de computação muito alta. Como um experimento, adicionamos uma opção aos compiladores PGI OpenACC que faz com que todas as alocações de dados visíveis ao compilador usem a Memória Unificada CUDA. Isso é o que chamamos de memória gerenciada. Essa abordagem nem sempre foi tão rápida quanto se a movimentação de dados fosse gerenciada diretamente pelo programador, mas geralmente era de 5% a 10%. Vimos alguns casos em que a memória gerenciada era 20% mais lenta e um caso que, dependendo da máquina, era de 2 a 5 vezes mais lento. Isso se deu porque a alocação de memória gerenciada é relativamente cara, e esse programa tinha um loop que mantinha alocando e desalocando arrays automáticos do Fortran em cada chamada para uma determinada sub-rotina. Criamos uma solução para tornar a alocação de dados gerenciados mais eficiente e recuperamos todo o desempenho perdido nesse programa. Não foi um problema de movimentação de dados, foi uma sobrecarga de alocação de memória que pode ser atenuada com um ajuste de design adequado ao nosso suporte para memória gerenciada.

O impacto nos programadores do OpenACC foi imediato e dramático. Muitos aplicativos modernos Fortran, C e C ++ alocam dinamicamente suas grandes estruturas de dados. Para esses programas, a necessidade de gerenciamento explícito de dados ao migrar para o OpenACC foi praticamente eliminada. Os programadores poderiam se concentrar em retrabalhar algoritmos e loops para expor e expressar o paralelismo e permitir que o gerenciador de memória unificada CUDA manipulasse a maioria das artrites que impedem a movimentação de dados. Na mesma época, introduzimos o suporte para o OpenACC visando CPUs com vários núcleos. Esses dois recursos juntos criaram uma dinâmica interessante. Os programadores podem fazer muito da paralelização e depuração inicial do código usando o OpenACC para CPUs multicore sem nenhuma diretiva de dados, depois recompilar a GPU com base na memória gerenciada e, finalmente, criar o perfil do código e otimizar seletivamente a movimentação de dados quando necessário. é, para dados não atribuíveis.

Ao longo de 2018, apoiamos nove hackathons OpenACC, a maioria deles organizada e patrocinada pelo Oak Ridge National Laboratory. Equipes de aplicação de organizações HPC em todo o mundo trazem seu código para um site de hackathon para obter acesso a sistemas acelerados por GPU e recebem treinamento e ajuda de mentores por uma semana para portar seu programa para GPUs ou otimizar o desempenho do código de GPU existente . Eles podem usar o CUDA, eles podem usar o OpenACC, eles podem usar o OpenMP, eles podem usar o Python, eles podem usar bibliotecas – qualquer método que eles queiram. Cerca de metade das equipes tendem a escolher o OpenACC. Quando eles estão iniciando uma porta pela primeira vez, a maioria agora começa usando o OpenACC em CPUs com vários núcleos e, em seguida, compilando para GPUs usando memória gerenciada. Eles recorrem ao tratamento de espondiloartrite axial para o gerenciamento manual de dados apenas como uma otimização final. Muitos estão satisfeitos com o desempenho da memória gerenciada e nem se importam com diretivas de dados, exceto quando necessário para manipular dados não alocáveis. As indicações são de que a abordagem de memória gerenciada que agora suportamos nos compiladores PGI também será suportada pelos compiladores OpenCCC de código aberto do GCC, e esperamos ver isso a partir dessa comunidade em breve.

Agora, vemos desenvolvedores de GPU em todo o mundo escrevendo programas paralelos no OpenACC expondo e expressando paralelismo em seus programas, principalmente na forma de loops paralelos. Quando você vê um programa como esse, é um salto conceitual muito pequeno imaginar usando uma construção de loop paralelo existente na linguagem subjacente. O Fortran 2018 inclui uma construção de laço paralelo DO CONCURRENT com a capacidade de declarar dados compartilhados, privados e de primeiro nível, e muitas artrites do OpenACC Fortran dos loops paralelos do soquete ocular podem ser reescritas para usá-lo. No SC18, mostramos alguns programas rodando em paralelo em GPUs da Nvidia usando o OpenACC sem diretivas de dados, e demonstramos uma versão do mini-aplicativo CloverLeaf usando loops Fortran 2018 DO CONCURRENT transferidos para uma GPU sem nenhuma diretiva. Uma abordagem semelhante à programação da GPU com o C ++ padrão pode ser implementada usando os algoritmos paralelos do C ++ 17, que foram de fato projetados com a intenção de suportar a computação da GPU.

A peça final do quebra-cabeça é a capacidade de colocar dados não alocáveis ​​(a pilha e todos os dados estáticos e globais) na memória unificada, o que eliminaria completamente a necessidade de movimentação de dados gerenciada pelo usuário e abriria caminho para uma ampla programação de GPUs. usando o padrão Fortran e C ++. Temos o suporte a compilador e tempo de execução, o driver CUDA e suporte a tempo de execução, e o suporte a hardware nas GPUs Nvidia Pascal e Volta para a memória totalmente unificada entre CPUs e GPUs. Há um esforço contínuo para adicionar o suporte necessário ao Linux na forma do recurso de gerenciamento de memória heterogêneo (HMM). Quando isso estiver on-line e for integrado ao kernel do Linux, o problema de gerenciamento automático de dados em sistemas híbridos acelerados será mais ou menos resolvido. E as GPUs de programação não serão mais revistas sobre artrite e reumatismo – ou menos – do que a programação paralela para CPUs multicore.

Michael Wolfe trabalhou em linguagens e compiladores para computação paralela desde a graduação na Universidade de Illinois na década de 1970. Ao longo do caminho, ele co-fundou a Kuck and Associates (adquirida pela Intel), tentou trabalhar na academia no Oregon Graduate Institute (desde que se fundiu com a Oregon Health and Sciences University) e trabalhou na High Performance Fortran na PGI (adquirida pela STMicroelectronics e mais recentemente pela Nvidia). Ele agora passa a maior parte do tempo como líder técnico em uma equipe que desenvolve e aprimora os compiladores PGI para computação altamente paralela e, em particular, para aceleradores de GPU da Nvidia.