Backends aceleradores

Es bastante sencillo describir un cálculo Tensor , pero cuándo y cómo se realiza ese cálculo dependerá de qué backend se utiliza para los Tensor y cuándo se necesitan los resultados en la CPU del host.

Detrás de escena, las operaciones en Tensor se envían a aceleradores como GPU o TPU , o se ejecutan en la CPU cuando no hay ningún acelerador disponible. Esto sucede automáticamente y facilita la realización de cálculos paralelos complejos utilizando una interfaz de alto nivel. Sin embargo, puede resultar útil comprender cómo se produce este envío y poder personalizarlo para obtener un rendimiento óptimo.

Swift para TensorFlow tiene dos backends para realizar cálculos acelerados: el modo ansioso de TensorFlow y X10. El backend predeterminado es el modo ansioso de TensorFlow, pero se puede anular. Hay disponible un tutorial interactivo que le guiará a través del uso de estos diferentes backends.

Modo ansioso de TensorFlow

El backend del modo ansioso de TensorFlow aprovecha la API de TensorFlow C para enviar cada operación Tensor a una GPU o CPU a medida que se encuentra. Luego se recupera el resultado de esa operación y se pasa a la siguiente operación.

Este envío operación por operación es sencillo de entender y no requiere configuración explícita dentro de su código. Sin embargo, en muchos casos no da como resultado un rendimiento óptimo debido a la sobrecarga que supone enviar muchas operaciones pequeñas, combinada con la falta de fusión y optimización de operaciones que puede ocurrir cuando hay gráficos de operaciones presentes. Finalmente, el modo ansioso de TensorFlow es incompatible con las TPU y solo se puede usar con CPU y GPU.

X10 (rastreo basado en XLA)

X10 es el nombre del backend de Swift para TensorFlow que utiliza el seguimiento de tensor diferido y el compilador de optimización XLA para, en muchos casos, mejorar significativamente el rendimiento en el envío operación por operación. Además, agrega compatibilidad para TPU , aceleradores específicamente optimizados para los tipos de cálculos que se encuentran en los modelos de aprendizaje automático.

El uso de X10 para cálculos Tensor no es el predeterminado, por lo que debes optar por este backend. Esto se hace especificando que se coloca un Tensor en un dispositivo XLA:

let tensor1 = Tensor<Float>([0.0, 1.0, 2.0], on: Device.defaultXLA)
let tensor2 = Tensor<Float>([1.5, 2.5, 3.5], on: Device.defaultXLA)

Después de ese punto, describir un cálculo es exactamente lo mismo que para el modo ansioso de TensorFlow:

let tensor3 = tensor1 + tensor2

Se pueden proporcionar más detalles al crear un Tensor , como qué tipo de acelerador usar e incluso cuál, si hay varios disponibles. Por ejemplo, se puede crear un Tensor en el segundo dispositivo TPU (suponiendo que sea visible para el host en el que se ejecuta el programa) usando lo siguiente:

let tpuTensor = Tensor<Float>([0.0, 1.0, 2.0], on: Device(kind: .TPU, ordinal: 1, backend: .XLA))

No se realiza ningún movimiento implícito de Tensor s entre dispositivos, por lo que si dos Tensor s en diferentes dispositivos se usan juntos en una operación, se producirá un error de tiempo de ejecución. Para copiar manualmente el contenido de un Tensor a un nuevo dispositivo, puede usar el inicializador Tensor(copying:to:) . Algunas estructuras de mayor escala que contienen Tensor s dentro de ellas, como modelos y optimizadores, tienen funciones auxiliares para mover todos sus Tensor s interiores a un nuevo dispositivo en un solo paso.

A diferencia del modo ansioso de TensorFlow, las operaciones que utilizan el backend X10 no se envían individualmente a medida que se encuentran. En cambio, el envío a un acelerador solo se activa leyendo los valores calculados al host o colocando una barrera explícita. La forma en que esto funciona es que el tiempo de ejecución comienza desde el valor que se lee en el host (o el último cálculo antes de una barrera manual) y rastrea el gráfico de cálculos que dan como resultado ese valor.

Este gráfico rastreado luego se convierte a la representación intermedia XLA HLO y se pasa al compilador XLA para optimizarlo y compilarlo para su ejecución en el acelerador. A partir de ahí, todo el cálculo se envía al acelerador y se obtiene el resultado final.

El cálculo es un proceso que requiere mucho tiempo, por lo que X10 se utiliza mejor con cálculos paralelos masivos que se expresan mediante un gráfico y se realizan muchas veces. Los valores hash y el almacenamiento en caché se utilizan para que los gráficos idénticos solo se compilen una vez para cada configuración única.

Para los modelos de aprendizaje automático, el proceso de capacitación a menudo implica un ciclo en el que el modelo se somete a la misma serie de cálculos una y otra vez. Querrá que cada uno de estos pases se vea como una repetición del mismo trazo, en lugar de un gráfico largo con unidades repetidas en su interior. Esto se habilita mediante la inserción manual de una llamada a la función LazyTensorBarrier() en las ubicaciones de su código donde desea que finalice el seguimiento.

Soporte de precisión mixta en X10

Se admite el entrenamiento con precisión mixta a través de X10 y se proporcionan API de bajo y alto nivel para controlarlo. La API de bajo nivel ofrece dos propiedades calculadas: toReducedPrecision y toFullPrecision , que convierten entre precisión total y reducida, junto con isReducedPrecision para consultar la precisión. Además de Tensor s, los modelos y optimizadores se pueden convertir entre precisión total y reducida utilizando esta API.

Tenga en cuenta que la conversión a precisión reducida no cambia el tipo lógico de un Tensor . Si t es un Tensor<Float> , t.toReducedPrecision también es un Tensor<Float> con una representación subyacente de precisión reducida.

Al igual que ocurre con los dispositivos, no se permiten operaciones entre tensores de diferentes precisiones. Esto evita la promoción silenciosa y no deseada a flotantes de 32 bits, que sería difícil de detectar para el usuario.