KomputarEl marco de cómputo de GPU de propósito general para tarjetas de gráficos de proveedores cruzados (AMD, Qualcomm, Nvidia & Friends) |
¿Únete a las llamadas de Discord & Community? Publicación de blog de documentación ⌨ ¿Ejemplos?
A continuación puede encontrar un ejemplo de multiplicación GPU usando las interfaces de Kompute C ++ y Python.
Puede unirse a Discord para preguntas / discusión, abrir un problema de GitHub o leer la documentación.
La interfaz C ++ proporciona acceso de bajo nivel a los componentes nativos de Kompute, lo que permite optimizaciones avanzadas, así como la extensión de los componentes.
void kompute ( const std::string& shader) {
// 1. Create Kompute Manager with default settings (device 0, first queue and no extensions)
kp::Manager mgr;
// 2. Create and initialise Kompute Tensors through manager
// Default tensor constructor simplifies creation of float values
auto tensorInA = mgr. tensor ({ 2 ., 2 ., 2 . });
auto tensorInB = mgr. tensor ({ 1 ., 2 ., 3 . });
// Explicit type constructor supports uint32, int32, double, float and bool
auto tensorOutA = mgr. tensorT < uint32_t >({ 0 , 0 , 0 });
auto tensorOutB = mgr. tensorT < uint32_t >({ 0 , 0 , 0 });
std::vector<std::shared_ptr<kp::Memory>> params = {tensorInA, tensorInB, tensorOutA, tensorOutB};
// 3. Create algorithm based on shader (supports buffers & push/spec constants)
kp::Workgroup workgroup ({ 3 , 1 , 1 });
std::vector< float > specConsts ({ 2 });
std::vector< float > pushConstsA ({ 2.0 });
std::vector< float > pushConstsB ({ 3.0 });
auto algorithm = mgr. algorithm (params,
// See documentation shader section for compileSource
compileSource (shader),
workgroup,
specConsts,
pushConstsA);
// 4. Run operation synchronously using sequence
mgr. sequence ()
-> record <kp::OpSyncDevice>(params)
-> record <kp::OpAlgoDispatch>(algorithm) // Binds default push consts
-> eval () // Evaluates the two recorded operations
-> record <kp::OpAlgoDispatch>(algorithm, pushConstsB) // Overrides push consts
-> eval (); // Evaluates only last recorded operation
// 5. Sync results from the GPU asynchronously
auto sq = mgr. sequence ();
sq-> evalAsync <kp::OpSyncLocal>(params);
// ... Do other work asynchronously whilst GPU finishes
sq-> evalAwait ();
// Prints the first output which is: { 4, 8, 12 }
for ( const float & elem : tensorOutA-> vector ()) std::cout << elem << " " ;
// Prints the second output which is: { 10, 10, 10 }
for ( const float & elem : tensorOutB-> vector ()) std::cout << elem << " " ;
} // Manages / releases all CPU and GPU memory resources
int main () {
// Define a raw string shader (or use the Kompute tools to compile to SPIRV / C++ header
// files). This shader shows some of the main components including constants, buffers, etc
std::string shader = ( R"(
#version 450
layout (local_size_x = 1) in;
// The input tensors bind index is relative to index in parameter passed
layout(set = 0, binding = 0) buffer buf_in_a { float in_a[]; };
layout(set = 0, binding = 1) buffer buf_in_b { float in_b[]; };
layout(set = 0, binding = 2) buffer buf_out_a { uint out_a[]; };
layout(set = 0, binding = 3) buffer buf_out_b { uint out_b[]; };
// Kompute supports push constants updated on dispatch
layout(push_constant) uniform PushConstants {
float val;
} push_const;
// Kompute also supports spec constants on initalization
layout(constant_id = 0) const float const_one = 0;
void main() {
uint index = gl_GlobalInvocationID.x;
out_a[index] += uint( in_a[index] * in_b[index] );
out_b[index] += uint( const_one * push_const.val );
}
)" );
// Run the function declared above with our raw string shader
kompute (shader);
}
El paquete Python proporciona una interfaz interactiva de alto nivel que permite la experimentación al tiempo que garantiza flujos de trabajo de desarrollo de alto rendimiento y rápido.
from . utils import compile_source # using util function from python/test/utils
def kompute ( shader ):
# 1. Create Kompute Manager with default settings (device 0, first queue and no extensions)
mgr = kp . Manager ()
# 2. Create and initialise Kompute Tensors through manager
# Default tensor constructor simplifies creation of float values
tensor_in_a = mgr . tensor ([ 2 , 2 , 2 ])
tensor_in_b = mgr . tensor ([ 1 , 2 , 3 ])
# Explicit type constructor supports uint32, int32, double, float and bool
tensor_out_a = mgr . tensor_t ( np . array ([ 0 , 0 , 0 ], dtype = np . uint32 ))
tensor_out_b = mgr . tensor_t ( np . array ([ 0 , 0 , 0 ], dtype = np . uint32 ))
assert ( t_data . data_type () == kp . DataTypes . uint )
params = [ tensor_in_a , tensor_in_b , tensor_out_a , tensor_out_b ]
# 3. Create algorithm based on shader (supports buffers & push/spec constants)
workgroup = ( 3 , 1 , 1 )
spec_consts = [ 2 ]
push_consts_a = [ 2 ]
push_consts_b = [ 3 ]
# See documentation shader section for compile_source
spirv = compile_source ( shader )
algo = mgr . algorithm ( params , spirv , workgroup , spec_consts , push_consts_a )
# 4. Run operation synchronously using sequence
( mgr . sequence ()
. record ( kp . OpTensorSyncDevice ( params ))
. record ( kp . OpAlgoDispatch ( algo )) # Binds default push consts provided
. eval () # evaluates the two recorded ops
. record ( kp . OpAlgoDispatch ( algo , push_consts_b )) # Overrides push consts
. eval ()) # evaluates only the last recorded op
# 5. Sync results from the GPU asynchronously
sq = mgr . sequence ()
sq . eval_async ( kp . OpTensorSyncLocal ( params ))
# ... Do other work asynchronously whilst GPU finishes
sq . eval_await ()
# Prints the first output which is: { 4, 8, 12 }
print ( tensor_out_a )
# Prints the first output which is: { 10, 10, 10 }
print ( tensor_out_b )
if __name__ == "__main__" :
# Define a raw string shader (or use the Kompute tools to compile to SPIRV / C++ header
# files). This shader shows some of the main components including constants, buffers, etc
shader = """
#version 450
layout (local_size_x = 1) in;
// The input tensors bind index is relative to index in parameter passed
layout(set = 0, binding = 0) buffer buf_in_a { float in_a[]; };
layout(set = 0, binding = 1) buffer buf_in_b { float in_b[]; };
layout(set = 0, binding = 2) buffer buf_out_a { uint out_a[]; };
layout(set = 0, binding = 3) buffer buf_out_b { uint out_b[]; };
// Kompute supports push constants updated on dispatch
layout(push_constant) uniform PushConstants {
float val;
} push_const;
// Kompute also supports spec constants on initalization
layout(constant_id = 0) const float const_one = 0;
void main() {
uint index = gl_GlobalInvocationID.x;
out_a[index] += uint( in_a[index] * in_b[index] );
out_b[index] += uint( const_one * push_const.val );
}
"""
kompute ( shader )Puede probar los cuadernos Interactive Colab que le permiten usar una GPU gratuita. Los ejemplos disponibles son los ejemplos de Python y C ++ a continuación:
Pruebe el colab C ++ interactivo de la publicación de blog | Prueba la publicación interactiva de Python Colab de Blog |
También puede consultar las dos conversaciones siguientes presentadas en la conferencia Fosdem 2021.
Ambos videos tienen marcas de tiempo que le permitirán omitir a la sección más relevante para usted: la introducción y las motivaciones para ambos son casi las mismas para que pueda omitir el contenido más específico.
Mira el video de los entusiastas de C ++ | Mira el video de los entusiastas de Python & Machine Learning |
La arquitectura central de Kompute incluye lo siguiente:
Para ver un desglose completo, puede leer más en la referencia de la clase C ++.
| Arquitectura completa | Componentes de kompute simplificados |
|---|---|
(Muy pequeño, consulte el diagrama de referencia completo en los documentos para más detalles) |
Kompute proporciona flexibilidad para ejecutar operaciones de manera asynrconosa a través de VK :: Ciens. Además, Kompute permite la asignación explícita de colas, que permiten la ejecución paralela de operaciones en las familias de colas.
La siguiente imagen proporciona una intuición sobre cómo las secuencias de kompute se pueden asignar a diferentes colas para habilitar la ejecución paralela en función del hardware. Puede ver el ejemplo práctico, así como la página de documentación detallada que describe cómo funcionaría usando un NVIDIA 1650 como ejemplo.
Kompute ha sido optimizado para trabajar en entornos móviles. El sistema de compilación habilita la carga dinámica de la biblioteca compartida Vulkan para entornos de Android, junto con un envoltorio de Android NDK en funcionamiento para los encabezados CPP.
Para una inmersión profunda completa, puede leer la publicación del blog "Supercargando sus aplicaciones móviles con GPU en el dispositivo GPU Aprendizaje automático". También puede acceder al código de ejemplo de extremo a extremo en el repositorio, que se puede ejecutar con Android Studio. |
Además del C ++ Core SDK, también puede usar el paquete Python de Kompute, que expone la misma funcionalidad central y admite la interoperabilidad con objetos de Python como listas, matrices numpy, etc.
Las únicas dependencias son Python 3.5+ y CMake 3.4.1+. Puede instalar Kompute desde el paquete Python Pypi usando el siguiente comando.
pip install kp
También puede instalar desde Master Branch usando:
pip install git+git://github.com/KomputeProject/kompute.git@master
Para obtener más detalles, puede leer la documentación del paquete Python o la documentación de referencia de la clase Python.
El sistema de compilación proporcionado utiliza cmake , que permite las compilaciones de plataformas cruzadas.
El nivel superior Makefile proporciona un conjunto de configuraciones optimizadas para el desarrollo, así como la compilación de la imagen de Docker, pero puede iniciar una compilación con el siguiente comando:
cmake -Bbuild
También puede agregar kompute en su repositorio con add_subdirectory : el archivo CMAKELISTS.txt de Android. Muestra cómo se haría esto.
Para obtener una descripción más avanzada de la configuración de compilación, consulte la documentación de buceo profundo del sistema de compilación.
Apreciamos PRS y problemas. Si desea contribuir, intente verificar la etiqueta "Buen primer problema", ¡pero incluso usar los problemas de Kompute e informar es una gran contribución!
Si desea ejecutar con capas de depuración, puede agregarlas con el parámetro KOMPUTE_ENV_DEBUG_LAYERS como:
export KOMPUTE_ENV_DEBUG_LAYERS="VK_LAYER_LUNARG_api_dump"
Para actualizar la documentación que necesitará:
make push_docs_to_ghpages Ejecutar las pruebas unitarias se ha simplificado significativamente para los contribuyentes.
Las pruebas se ejecutan en CPU, y se pueden activar utilizando la interfaz de línea de comandos ACT (https://github.com/nektos/act) - Una vez que instale la línea de comando (e inicie el Docker Daemon) solo tiene que escribir:
$ act
[Python Tests/python-tests] Start image=axsauze/kompute-builder:0.2
[C++ Tests/cpp-tests ] Start image=axsauze/kompute-builder:0.2
[C++ Tests/cpp-tests ] ? docker run image=axsauze/kompute-builder:0.2 entrypoint=["/usr/bin/tail" "-f" "/dev/null"] cmd=[]
[Python Tests/python-tests] ? docker run image=axsauze/kompute-builder:0.2 entrypoint=["/usr/bin/tail" "-f" "/dev/null"] cmd=[]
...
El repositorio contiene pruebas unitarias para el código C ++ y Python, y se puede encontrar en la carpeta test/ y python/test .
Las pruebas se ejecutan actualmente a través del CI utilizando acciones de GitHub. Utiliza las imágenes que se encuentran en docker-builders/ .
Para minimizar los requisitos de hardware, las pruebas pueden ejecutarse sin una GPU, directamente en la CPU usando SwiftShader.
Para obtener más información sobre cómo se configuran el CI y las pruebas, puede ir a la sección CI, Docker y Pruebas en la documentación.
Este proyecto comenzó después de ver que muchos proyectos ML & DL nuevos y reconocidos como Pytorch, TensorFlow, Alibaba DNN, Tencent NCNN, entre otros, han integrado o están buscando integrar el SDK Vulkan para agregar soporte de GPU móvil (y Vendor cruzado).
El SDK de Vulkan ofrece una gran interfaz de bajo nivel que habilita las optimizaciones altamente especializadas, sin embargo, tiene un costo de código altamente detallado que requiere 500-2000 líneas de código incluso para comenzar a escribir código de aplicación. Esto ha resultado en que cada uno de estos proyectos tenga que implementar la misma línea de base para abstraer las características no relacionadas con el SDK de Vulkan. Esta gran cantidad de placa de caldera no estandarizada puede dar lugar a una transferencia de conocimiento limitada, una mayor probabilidad de que se introduzcan errores de implementación de marco únicos, etc.
Actualmente estamos desarrollando Kompute para no ocultar la interfaz Vulkan SDK (ya que está increíblemente bien diseñada) sino para aumentarla con un enfoque directo en las capacidades de computación GPU de Vulkan SDK. Este artículo proporciona una descripción general de alto nivel de las motivaciones de Kompute, junto con un conjunto de ejemplos prácticos que introducen la computación de GPU y la arquitectura central de Kompute.