ByteDance Spark admite la práctica de inferencia del modelo Wanka

Resumen: este artículo está compilado del discurso de apertura de "ByteDance Spark apoya la práctica de inferencia de modelos Wanka" en CommunityOverCode Asia 2023 por el ingeniero de infraestructura de ByteDance Liu Chang y el ingeniero de sistemas de aprendizaje automático de ByteDance Zhang Yongqiang.
En el proceso de desarrollo de la nativación de la nube, Kubernetes ha permitido que cada vez más tipos de aplicaciones de carga, incluidos big data e inteligencia artificial, migren a Kubernetes debido a sus sólidas capacidades de construcción ecológica y su influencia. Byte explora internamente la migración de Spark de Hadoop a Kubernetes. ejecuta trabajos nativos de la nube. La arquitectura de gestión de recursos de big data de ByteDance y la evolución de la implementación de Spark se pueden dividir aproximadamente en tres etapas:
  • La primera etapa es la gestión de recursos fuera de línea basada completamente en YARN. Al utilizar YARN a gran escala para gestionar grandes clústeres de datos, la utilización de recursos de Spark se puede mejorar de manera efectiva y al mismo tiempo reducir los costos de operación y mantenimiento de los recursos.
  • La segunda etapa es la etapa de implementación mixta de recursos fuera de línea. Al construir un clúster de implementación híbrido de YARN y Kubernetes, se mejora aún más la utilización general de los recursos fuera de línea. A través de la tecnología de implementación híbrida, se ha mejorado significativamente la utilización de recursos tanto de clústeres como de máquinas individuales. Una mayor mejora en la utilización de recursos significa la necesidad de medios de aislamiento más completos. Por lo tanto, comenzamos a promover gradualmente la implementación en contenedores de Spark.
  • La tercera etapa es una implementación completa nativa de la nube. Las cargas fuera de línea ya no utilizan diferentes arquitecturas para la administración, y la pila de tecnología y el grupo de recursos están verdaderamente unificados. La naturaleza nativa de la nube de Spark también se construye y mejora gradualmente.
Por supuesto, la nube nativa es una tendencia de desarrollo casi unánime en la industria, entonces, ¿por qué utilizar la nube nativa? ¿Por qué utilizar Kubernetes como base de gestión de recursos unificada? Hay tres ventajas principales: la primera es la operación y el mantenimiento eficientes . Kubernetes proporciona una creación y gestión de carga ágiles, ya sea carga en línea o carga de big data, y puede lograr fácilmente un desarrollo, integración e implementación continuos. El segundo es la agrupación de recursos . La base unificada nativa de la nube reduce la sobrecarga de la infraestructura y mejora aún más la eficiencia de la transferencia de recursos. En términos de utilización de recursos, la tasa de utilización de todo el centro de datos se puede mejorar de manera más completa y completa, lo que aumenta la eficiencia. . El tercero es la prosperidad ecológica . Sabemos que Kubernetes tiene casi el ecosistema más activo. Promueve el desarrollo ecológico en todos los niveles al proporcionar definiciones de interfaz estandarizadas, ya sean instalaciones básicas de operación y mantenimiento, administración de aplicaciones de capa superior o redes y almacenamiento subyacentes. .etc., hay muchas opciones de gestión que facilitan el uso nativo de la nube de Spark.

Escala ByteDance Spark

ByteDance tiene la escala comercial Spark líder en la industria, ejecuta millones de trabajos fuera de línea todos los días, ocupa recursos de millones de núcleos, decenas de miles de tarjetas GPU y el tamaño total del clúster alcanza decenas de miles de nodos. Una carga de Spark a tan gran escala significa que no es fácil implementar Spark de forma completamente nativa. Estas son las preguntas en las que pensamos en la práctica. ¿La implementación de trabajos de Spark es una implementación estática de forma independiente o una implementación dinámica de K8s Native? ¿Se debe utilizar el operador? ¿Cómo implementar la gestión de recursos a nivel de inquilino y el control de los trabajos de Spark en K8? ¿Se debe realizar la gestión cuando se envía el trabajo o cuando se crea el Pod? ¿Cómo satisfacer las necesidades de programación de Spark? Al enviar trabajos en Spark, ¿la gran cantidad de creaciones de Pod provoca cuellos de botella en la programación? Para una migración de arquitectura operativa a tan gran escala, ¿cómo construimos capacidades periféricas y suavizamos la experiencia antes y después de la migración operativa?
En el proceso de exploración de la nativación de la nube por parte de Spark, los socios también enfrentaron muchos problemas. Las tareas de búsqueda incluyen una gran cantidad de tareas de procesamiento por lotes fuera de línea con requisitos de GPU extremadamente altos. Los negocios de clústeres en línea pueden liberar una gran cantidad de recursos durante los picos bajos, y algunos. Los servicios en línea no se pueden utilizar por completo y la utilización general de la GPU es baja. El aprendizaje automático es un socio importante de Spark. Resolvemos los problemas anteriores y trabajamos juntos para fortalecer el ecosistema circundante. Spark ha realizado mejoras específicas en el motor para la empresa y la empresa también se ha beneficiado de los recursos, la programación y la gestión nativos de la nube de Spark. .

Soluciones nativas de la nube Spark y mejoras del motor

Los métodos de uso actuales de las soluciones tecnológicas nativas de la nube Spark incluyen Spark Native y Spark Operador de código abierto de Google. Los dos métodos logran el mismo objetivo y, en última instancia, llaman a la herramienta de línea de comando Spark-submit. La diferencia es que Spark Operador de Google admite una semántica más rica e inyecta funciones más ricas más cercanas a los K8 a través de Operador y Mutatingwebhook.
Hay dos soluciones de tecnología nativa de la nube de Byte Spark. La primera es la migración sin problemas, que no necesita modificar el método de envío de YARN. Se envía a Kubelet o Gödel para su programación a través de Yodel. enviado al sistema de programación a través de Arcee. Los conceptos que deben explicarse aquí son: Gödel es un sistema de programación de recursos distribuidos desarrollado por Byte. Aloja las capacidades de programación de recursos de YARN y Kubernetes y unifica el grupo de recursos, la cuota, la programación y el aislamiento de Kubernetes y Yodel; por el propio Byte Operador que admite tipos de trabajos de YARN y transforma los componentes RM y NM de YARN. Arcee es un operador unificado de big data nativo de la nube desarrollado independientemente por Byte, que puede administrar de manera más conveniente cargas de big data como Spark y Flink. La diferencia entre Yodel y Arcee es que Yodel es una solución de big data de Gödel que es "compatible con el protocolo YARN", mientras que Arcee es una solución de big data de Gödel que es "compatible con el protocolo K8s". reutilizará las mismas tecnologías Gödel Scheduler y Kubelet.
Esta práctica es una implementación completa nativa de la nube, que se envía a través de Arcee Operador. Las capacidades principales de Arcee incluyen principalmente la gestión del ciclo de vida del trabajo, la gestión de recursos del trabajo y algunas funciones de personalización del motor.

Introducción a Arcee

La idea central de diseño de Arcee es la gestión de trabajos de dos niveles , que se basa en el modelo de gestión de dos niveles de YARN: el servicio de gestión central AM, que es el principal responsable de crear y mantener trabajos de big data, y luego AM crea y mantiene la informática. trabajadores. En correspondencia con el trabajo de Spark, Arcee crea el Controlador y el Controlador crea el Ejecutor requerido. Este modelo de gestión puede gestionar y expresar eficazmente el estado del trabajo de big data y personalizar las estrategias de gestión del trabajo. Por otro lado, también puede garantizar que el motor informático tenga control total sobre la operación de los trabajos informáticos y tenga la capacidad de ajustar el uso de recursos según sea necesario.
La arquitectura general se muestra en la figura. Arcee Operador contiene seis módulos . El módulo Arcee CRD define dos tipos de recursos: ArceeApplication y ArceeCommand: ArceeApplication se usa para describir trabajos específicos y ArceeCommand describe las operaciones utilizadas para trabajos. utilizado Inyección de configuración y verificación para Application/Pod; Application Manager es responsable de la gestión del ciclo de vida de los trabajos; PodSetManager es la gestión de recursos de trabajos; EngineManager es la gestión del motor, que se utiliza para implementar algunas capacidades de personalización del motor; para completar trabajos de interfaz de big data como Spark con programadores por lotes.
El proceso completo de envío de trabajos consiste en que Arnold (plataforma de aprendizaje automático) inicia el envío de trabajos de Spark, llama a Spark Client y completa los parámetros requeridos para enviar el trabajo a K8. En el modo Arcee, Spark Client utiliza el Arcee Client integrado para crear Spark ArceeApplication, que Webhook preprocesa y envía a APIServer. A continuación, el Controlador Arcee recibe el evento de creación de la Aplicación. El Arcee ApplicationManager genera el estado del trabajo correspondiente y crea el Controlador de acuerdo con la descripción en la Aplicación. El Controlador crea los Ejecutores requeridos bajo demanda. Arcee continuará monitoreando a todos los Ejecutores y. También realizará la inyección de configuración relacionada. Todos los Pods del Controlador y Ejecutor en la Aplicación se mantendrán en PodsetManager de Arcee para obtener estadísticas de uso de recursos y proporcionar información relevante a otros módulos.

Chispa en Arcee

Spark en Arcee puede considerarse una mejora del modelo de implementación Spark Native hasta cierto punto. La principal diferencia es que el cliente K8s integrado en Spark Client es reemplazado por el componente responsable de administrar la carga del controlador. se convierte en Operador de Arcee y el Ejecutor se vuelven independientes entre sí. Tener una Aplicación Arcee unificada para el mantenimiento. Arcee también proporciona gestión del ciclo de vida del trabajo, programación de blindaje y otras funciones relacionadas.

Optimización del motor Spark

Con base en la práctica de antecedentes comerciales presentada en la sección anterior, el lado del motor Spark ha realizado las siguientes mejoras. Las siguientes son la ocurrencia y las soluciones de cada problema.
  • El ejecutor sale con gracia para evitar anomalías en el estado de MPS
      Actualmente, algunos trabajos de vaciado de bases de datos de Spark que requieren el uso de GPU se ejecutan en K8 y se combinan con servicios en línea. Estos trabajos comparten el dispositivo GPU en el host a través de MPS (MPS es la tecnología de servicio multiproceso proporcionada por Nvidia, que permite diferentes). procesos al mismo tiempo. El proceso realiza multiplexación por división de espacio en la GPU en lugar de la multiplexación por división de tiempo predeterminada. Si uno de los múltiples procesos compartidos se cancela al ejecutar Kernel, es fácil causar una excepción fatal a nivel de hardware. lo que hará que otros procesos en la GPU salgan, por lo que es necesario manejar la salida elegante de cada proceso.
      La ejecución en K8 puede provocar que el contenedor sea desalojado o eliminado debido al agotamiento de recursos debido a ciertas razones de programación. Analizamos cuidadosamente las situaciones de salida de varios Ejecutores y Trabajadores de las relaciones Conductor, Ejecutor, Demonio y Trabajador. Al implementar la salida elegante de Executor en el entorno del contenedor, capturar la señal de salida y realizar cudaDeviceSync automáticamente, se evita que MPS esté en un estado indefinido debido a una salida fuera de línea.
  • Resuelva una gran cantidad de problemas de pods pendientes a través de cuotas
      Spark admite DynamicAllocation. En el uso real, los usuarios generalmente configuran Max en un valor relativamente grande. Actualmente, para evitar que se genere una gran cantidad de Pods pendientes, Arnold realiza la verificación de cuota basada en Max solo cuando la cuota es suficiente para iniciar. Los ejecutores pueden enviarse realmente a los K8; de lo contrario, esperarán en la cola en el servicio Arnold. Sin embargo, la desventaja actual de utilizar Max to Check Quota es que es fácil desperdiciar recursos. Si hay una cuota en la cola menor que Max, de acuerdo con las características de la tarea actual, la tarea se puede iniciar primero para usar los recursos actuales. Sin embargo, la lógica de verificación de cuota actual hace que esta parte del recurso sea. inutilizable y la tarea siempre está en cola en la capa superior. Este problema se puede solucionar mediante los siguientes medios:
    • Utilice el parámetro Spark.kubernetes.allocation.batch.size para controlar la cantidad de Pods extraídos en cada lote;
    • Limite la cantidad máxima de Pening Pods para un solo trabajo mediante el parámetro Spark.kubernetes.allocation.maxPendingPods;
    • Sin embargo, el ajuste de parámetros aún no puede resolver el problema de una gran cantidad de envíos en la misma cola en el mismo período de tiempo. Por lo tanto, puede usar Webhook para verificar la cuota según la cola. Si no hay una cuota, la creación del Pod falla. Spark maneja la excepción, agrega una estrategia de creación de Pod, aumenta exponencialmente el intervalo de tiempo de creación, etc. para resolver este problema.
  • Optimización sólida de operaciones en escenarios de recursos no estables de ubicaciones mixtas
Para dar algunos ejemplos, a menudo se encuentra que Spark Executor Pod se rechaza anormalmente (UnexpectedAdmissionError) durante múltiples pruebas de estrés durante la programación de optimización de la estabilidad de los recursos. A través de una investigación centralizada, se solucionaron múltiples problemas de condiciones de carrera en una serie de lógica de Kubelet y el recurso mixto diario promedio alcanzó un aumento estable en la tasa de llenado límite. También llevamos a cabo una serie de ajustes y transformaciones, agregando algunos puntos de recopilación de indicadores de GPU para facilitar la observación del uso de recursos y mejorando la tolerancia a fallas de la tarea ante la inestabilidad de los recursos a través de parámetros como Lista negra y Especulación.

Integración ecológica circundante

En el entorno Spark en K8, los registros y los indicadores de monitoreo también son muy importantes. Pueden ayudarnos a observar el estado de ejecución de todo el clúster, el contenedor y la tarea, localizar rápidamente problemas según los registros y el monitoreo, y manejarlos de manera oportuna. . Por lo tanto, se construyó gradualmente un sistema Trace durante el proceso de naturalización de la nube Spark. Arcee Operador y el programador Gödel proporcionan algunos indicadores de trabajo, Spark proporciona indicadores comerciales y el componente independiente Metrics Collector proporciona indicadores de máquinas físicas e indicadores de contenedores. En términos de registros, el agente de registro que se ejecuta en cada nodo recopila registros de rutas específicas y los carga automáticamente en la plataforma de registros para su análisis y consulta. Todos los indicadores y registros se pueden consultar en tiempo real según la plataforma de capacitación de aprendizaje automático de Arnold. También se proporcionan tablas de datos específicas. Los usuarios pueden realizar consultas de nivel superior según sus necesidades, como producción de informes, optimización del trabajo, descubrimiento de anomalías en el trabajo. etc. . Al mismo tiempo, Arnold también puede ponerse al día con las actualizaciones de manera oportuna a través de la gestión de imágenes.

Práctica de razonamiento del modelo Wanka.

Nuestros grupos actuales se dividen principalmente en grupos fuera de línea y grupos en línea. El grupo fuera de línea se centra principalmente en tareas de capacitación y se centra principalmente en el rendimiento de tareas. Hay tarjetas como V100, A100 y A800. y se centra en la latencia y el rendimiento, principalmente tarjetas más pequeñas como T4, A10 y A30, con decenas de miles de tarjetas GPU en total.

principal contradicción

La principal contradicción actual es que la cuota del clúster fuera de línea se ha asignado por completo. Lógicamente, los recursos se han asignado, pero todavía hay mucho margen de mejora en la utilización general del clúster fuera de línea. Además, hay muchas necesidades informáticas internas que no se han cubierto. Por ejemplo, nuestro grupo es como un contenedor grande. Estas tareas de alta calidad son en realidad como piedras. Las piedras pueden estar llenas, pero todavía hay muchos espacios entre las piedras. arena, por lo que nuestra definición del problema es encontrar estos huecos y llenarlos con arena, es decir, encontrar recursos reutilizables adecuados y proponer tareas adecuadas.

recurso

Clúster sin conexión: tareas de baja calidad

La primera son las tareas de baja prioridad en el clúster fuera de línea. Esta parte está en el clúster fuera de línea en su conjunto y no es sensible a retrasos. Usamos estos recursos inactivos con baja prioridad y programamos tareas de baja prioridad cuando hay tiempo libre. Luego, cuando haya tareas de alta prioridad, se adelantarán a ellas. Al mismo tiempo, estos son los recursos de toda la tarjeta y su suministro no tiene reglas obvias, porque el envío fuera de línea en sí no tiene reglas obvias y el nivel de aislamiento general es relativamente bajo.

En línea -> Fuera de línea: Marea

La otra parte son los recursos de marea de en línea a fuera de línea. Esta parte requiere prestar los recursos inactivos del clúster en línea al clúster fuera de línea. Lo implementamos en base a Virtual-Kubelet. Esta parte también es el recurso de tarjeta completo, y su suministro es. aleatorio Hay un patrón obvio con los altibajos del negocio. Cuando el negocio en línea está en su punto máximo, los recursos se liberan mediante escalado automático y luego se prestan al clúster fuera de línea. se expande nuevamente y el clúster expulsará el Pod fuera de línea. Este es un nivel de aislamiento medio, el Pod fuera de línea debe ejecutarse en la misma máquina, pero la tarjeta aún se puede aislar.

En línea->Fuera de línea: coubicación normal

La otra parte son los recursos de ubicación conjunta normales de en línea a fuera de línea. Esta parte en realidad significa que prestamos parte de la potencia informática de la GPU relativamente poco utilizada en el clúster en línea al clúster fuera de línea. no use toda la tarjeta y esté vacía. La potencia informática se puede reutilizar y la implementación general se basa en Virtual-Kubelet + ByteCUDA + MPS.
ByteCUDA es un gancho CUDA de desarrollo propio. Realiza algunos trabajos en el aislamiento de la memoria y la multiplexación por división de tiempo en la capa superior. El MPS de la capa inferior mejora el nivel de aislamiento general mediante la multiplexación por división de espacio. Lo que se presta es en realidad un recurso de tarjeta no integrado, es decir, una tarjeta se puede utilizar para múltiples propósitos. Esta tarjeta puede tener tareas en línea y tareas fuera de línea. La ventaja es que el volumen de suministro es relativamente estable. Esta parte del servicio no se expande ni reduce automáticamente. Todos se ejecutan en la misma tarjeta y tienen el nivel de aislamiento más alto.
Una gran pregunta que tenemos sobre la coubicación normal es ¿cómo evitar que el offline afecte al online? En primer lugar, se debe realizar el aislamiento de la memoria y el aislamiento de la potencia informática. Además, utilizamos un algoritmo de préstamo dinámico adaptable a la carga, o estrategia de préstamo, para observar parte del consumo de energía de la GPU dentro de un período de ventana y luego emitir juicios basados ​​en él. estos indicadores. ¿Deberían nuestros cálculos fuera de línea evitar activamente las solicitudes de cálculo en línea para que el mundo en línea se vea menos afectado?
Además, MPS es famoso por su problema de propagación de fallas. El problema mencionado anteriormente se resuelve mediante una salida elegante. En las representaciones anteriores, podemos ver que el rendimiento en línea antes y después de la colocación casi no cambia y el retraso aumenta en aproximadamente 0,75 ms. De hecho, es aceptable. Su tasa de utilización ha aumentado del 10% original al 70%. Esto tiene un pequeño impacto en los ingresos generales, pero la tasa de utilización ha mejorado enormemente.

Tarea

Después de los recursos están las tareas, que es lo que llamamos arena. La primera es que su demanda debe ser lo suficientemente grande, de lo contrario no habrá necesidad de "tocar el violín". Además, debido a que se trata de recursos fragmentados, el tamaño de las tareas requeridas debe ser relativamente moderado y no pueden ser tareas particularmente grandes. También es necesario que estas tareas no puedan consumir en gran medida estos recursos no aislados, como discos, redes, etc. Además, las tareas también deben poder adaptarse a la expansión y contracción automática de recursos, porque estos recursos son elásticos; Los recursos y deben usarse automáticamente al expandir las tareas. Los recursos, cuando se reducen, no se verán obstaculizados por esta reducción.

Tareas de razonamiento fuera de línea basadas en Spark

Según los requisitos de implementación anteriores, la tarea de razonamiento fuera de línea basada en Spark finalmente se bloqueó. En primer lugar, debido a que existe una gran cantidad de requisitos internos de razonamiento fuera de línea, la demanda es bastante grande y el proceso de la tarea de razonamiento es relativamente simple; Y no hay nada entre los Ejecutores. Para las necesidades de comunicación, no se necesitan tarjetas en línea, RDMA, etc. Además, una gran cantidad de datos se almacena en HDFS y Hive, que naturalmente es compatible con Spark; Al mismo tiempo, también necesitamos utilizar las capacidades de procesamiento y distribución de datos de Spark y la capacidad de expandir y reducir dinámicamente la capacidad según la asignación dinámica.

compilación del SDK

Después de bloquear la tarea, lo que tenemos que hacer es encapsular las mejores prácticas tanto como sea posible. Lo anterior es un diagrama esquemático del SDK, que es un Tide Box que admite inferencias de modelos comunes como Pytorch y Tensorflow, y también admite inferencias de modelos comunes. Puntos de control a nivel de partición. De esta manera, no es necesario repetir los cálculos cuando se retiran recursos, lo que puede evitar el desperdicio de potencia informática y mejorar la utilización general de los recursos al admitir el procesamiento por lotes.

Construcción de plataformas

Una vez creado el SDK, la construcción de la plataforma también es un aspecto muy importante. No queremos que los usuarios ejecuten comandos directamente a través de Spark Submit, lo cual es incómodo de controlar. Por lo tanto, la plataforma de aprendizaje automático Arnold se utiliza como base para administrar estos recursos de manera unificada. Basado en el desarrollo y la depuración de Notebook, las variables necesarias se pueden configurar de antemano. Los usuarios también pueden depurar en un solo paso sin buscar Enviar manualmente. Es relativamente conveniente cambiar recursos en diferentes escenarios al mismo tiempo.
Además, el método de inicio de la tarea puede admitir múltiples métodos, como activación de eventos ascendentes, activación de tiempo, activación de API, etc., lo que facilita el uso del usuario. Por ejemplo, si desea organizar algunos requisitos de automatización de usuarios o canalizaciones, puede implementarlos de manera flexible a través de estos métodos. Además, la operación y el mantenimiento de las tareas también son muy importantes. Puede realizar la capacidad de ver el historial de tareas y el seguimiento de problemas con un solo clic.

Coincidencia de tareas y recursos

Hay muchos tipos de tareas de inferencia de Spark: una es la demanda de emergencia repentina. Esta parte de la demanda de recursos es relativamente grande, el tiempo también es urgente y, por lo general, es una demanda no convencional. Para esta parte, necesitamos utilizar una optimización baja fuera de línea. y Tide es un recurso con tarjetas más completas y mayor potencia informática. Además, es necesario realizar un seguimiento por lotes de las tareas que requieren reejecuciones periódicas, tienen requisitos de recursos relativamente grandes y una urgencia de tarea promedio, y utilizan estos recursos en implementaciones mixtas normales y de marea. Las tareas de rutina pueden ser de nivel diario y tener requisitos de recursos medios. Utilizaremos un suministro relativamente más estable y más estable de recursos de coubicación normales para respaldarlas.
El estado máximo desde los préstamos en línea hasta fuera de línea es de aproximadamente 10000+ Tidal GPU; la implementación mixta normal es de aproximadamente 20000+. Esta parte también se debe a la implementación mixta fuera de línea, la utilización general se ha duplicado, hay alrededor de 100+ tareas de inferencia fuera de línea cada vez. día y una sola tarea tiene un límite máximo de 5K+ GPU. Limitamos activamente esta parte; de ​​lo contrario, los usuarios pueden aumentarla aún más, lo que resulta en que todos los recursos estén ocupados. Una tarea típica de limpieza de bases de datos requiere recursos estables para ejecutarse durante 9,5 días. A través de este recurso elástico el cronograma se redujo a 7,5 horas para completarse.

perspectiva del futuro

En el futuro, nuestros recursos flexibles de coubicación deberán hacer más, no solo para mejorar la utilización general de los recursos, sino también para optimizarlos. Primero, tratar de evitar el impacto en las operaciones en línea, para que se pueda usar más fuera de línea y lograr una mayor tasa de utilización. Además, todavía necesitamos conectar más empresas para expandir la escala general y los ingresos para mejorar el rendimiento general; y al mismo tiempo reducir o evitar en la medida de lo posible los problemas relacionados causados ​​por la reversión innecesaria de recursos.
¡Compañero pollo deepin-IDE de "código abierto" y finalmente logró el arranque! Buen chico, Tencent realmente ha convertido Switch en una "máquina de aprendizaje pensante" Revisión de fallas de Tencent Cloud del 8 de abril y explicación de la situación Reconstrucción de inicio de escritorio remoto de RustDesk Cliente web Base de datos de terminal de código abierto WeChat basada en SQLite WCDB marcó el comienzo de una actualización importante Lista de abril de TIOBE: PHP cayó a un mínimo histórico, Fabrice Bellard, el padre de FFmpeg, lanzó la herramienta de compresión de audio TSAC , Google lanzó un modelo de código grande, CodeGemma , ¿te va a matar? Es tan bueno que es de código abierto: herramienta de edición de carteles e imágenes de código abierto
{{o.nombre}}
{{m.nombre}}

Supongo que te gusta

Origin my.oschina.net/u/5941630/blog/10581528
Recomendado
Clasificación