El aumento de la complejidad del código es un desafío clave para la productividad de la ingeniería de software. La finalización del código ha sido una herramienta esencial que ha ayudado a reducir esta complejidad en los entornos de desarrollo integrado (IDE). Tradicionalmente, las propuestas de finalización de código se implementan utilizando motores semánticos (SE) basados en reglas, que normalmente tienen acceso al repositorio completo y comprenden su estructura semántica. Investigaciones recientes han demostrado que los modelos de lenguaje grandes (por ejemplo, Codex y PaLM) permiten propuestas de código más largas y complejas, y como resultado han surgido productos útiles (por ejemplo, Copilot). Sin embargo, la pregunta sigue siendo cómo la finalización de código asistida por aprendizaje automático (ML) afecta la productividad del desarrollador más allá de la productividad percibida y las sugerencias aceptadas.
Hoy describimos cómo combinamos ML y SE para crear una novedosa finalización de código ML semántica híbrida basada en Transformer que ahora está disponible para los desarrolladores internos de Google. Discutimos cómo se pueden combinar ML y SE (1) recalificando las propuestas de token único de SE con ML, (2) aplicando completaciones de una sola línea y de varias líneas con ML y verificando la corrección con el SE, o (3) de una sola línea y de varias líneas. continuación de sugerencias semánticas para tokens únicos por ML. Comparamos la finalización del código de aprendizaje automático semántico híbrido por parte de más de 10 000 Googlers (durante tres meses en ocho lenguajes de programación) con un grupo de control y observamos una reducción del 6 % en el tiempo de iteración de codificación (tiempo entre compilaciones y pruebas) y una reducción del contexto del 7 %. cambiar (es decir, dejar el IDE) cuando están expuestos a una finalización de ML de una línea. Estos resultados muestran que la combinación de ML y SE puede mejorar la productividad de los desarrolladores. Actualmente, el 3% del código nuevo (medido en caracteres) se genera al aceptar sugerencias de finalización de ML.
Transformadores para completar
Un enfoque común para la finalización del código es entrenar modelos transformadores que utilicen un mecanismo de autoconciencia para la comprensión del lenguaje a fin de permitir la comprensión del código y las predicciones de finalización. Tratamos el código de manera similar al lenguaje, representado por tokens de subpalabras y un vocabulario de SentencePiece, y usamos modelos de codificador-descodificador-transformador que se ejecutan en TPU para hacer predicciones de finalización. La entrada es el código que rodea al cursor (~1000-2000 tokens) y la salida es una serie de sugerencias para completar la línea o líneas actuales. Las secuencias se generan con una búsqueda de haz (o exploración de árbol) en el decodificador.
Durante el entrenamiento para monorepo de Google, escapamos el resto de una línea y algunas líneas siguientes para imitar el código que se está desarrollando activamente. Entrenamos un solo modelo en ocho lenguajes (C++, Java, Python, Go, Typescript, Proto, Kotlin y Dart) y observamos un rendimiento mejorado o igual en todos los lenguajes, eliminando la necesidad de modelos dedicados. Además, encontramos que un tamaño de modelo de ~500 millones de parámetros es un buen compromiso para una alta precisión de predicción con baja latencia y bajo costo de recursos. El modelo se beneficia enormemente de la calidad del monorepo, que se aplica a través de políticas y revisiones. Para las sugerencias de varias filas, aplicamos iterativamente el modelo de una sola fila con umbrales aprendidos para decidir si comenzar a predecir las finalizaciones para la siguiente fila.
![]() |
Los modelos de codificador-decodificador-transformador se utilizan para predecir el resto de la línea o líneas de código. |
Reclasificación de propuestas de tokens individuales con ML
A medida que un usuario escribe en el IDE, el modelo ML y el SE solicitan de forma interactiva la finalización del código simultáneamente en el backend. El SE generalmente solo predice un solo token. Los modelos de ML que usamos predicen múltiples tokens hasta el final de la fila, pero solo consideramos el primer token que coincide con las predicciones del SE. Identificamos las tres principales propuestas de ML que también se incluyen en las propuestas de SE y las clasificamos en la parte superior. Los resultados reclasificados se muestran luego como sugerencias para el usuario en el IDE.
En la práctica, nuestros SE se ejecutan en la nube y brindan servicios de lenguaje (por ejemplo, finalización semántica, diagnóstico, etc.) con los que los desarrolladores están familiarizados, por lo que organizamos los SE para que se ejecuten en los mismos lugares que las TPU que realizan la inferencia de ML. Los SE se basan en una biblioteca interna que proporciona una funcionalidad similar a la de un compilador con latencias bajas. Debido a la configuración del diseño, donde las solicitudes se ejecutan en paralelo y ML suele ser más rápido de atender (alrededor de 40 ms en promedio), no agregamos latencia a las finalizaciones. Observamos una mejora significativa de la calidad en el uso real. En el 28% de las ofertas aceptadas, el rango de la oferta es más alto debido al impulso, en el 0,4% de los casos es peor. También encontramos que los usuarios escriben >10% menos caracteres antes de aceptar una sugerencia de finalización.
Compruebe las finalizaciones de ML de una o varias líneas para verificar la corrección semántica
En el momento de la inferencia, los modelos de ML normalmente desconocen el código fuera de su ventana de entrada, y el código visto durante el entrenamiento puede pasar por alto las adiciones agregadas recientemente requeridas para completar en repositorios que cambian activamente. Esto conduce a un inconveniente común de la finalización de código asistida por ML, donde el modelo puede sugerir un código que parece correcto pero no se compila. Según la investigación interna de la experiencia del usuario, este problema puede erosionar la confianza del usuario con el tiempo y reducir las ganancias de productividad.
Usamos SE para realizar verificaciones rápidas de corrección semántica dentro de un presupuesto de latencia dado (<100 ms para finalización de extremo a extremo) y usamos árboles de sintaxis abstracta en caché para permitir una comprensión estructural "completa". Las verificaciones semánticas típicas incluyen la resolución de referencias (es decir, existe este objeto), verificaciones de invocación de métodos (p. ej., confirmar que el método se invocó con un número correcto de parámetros) y verificaciones de asignación (para confirmar que el tipo es el esperado).
Por ejemplo, para el lenguaje de programación Go, aproximadamente el 8 % de las sugerencias contienen errores de compilación antes de la verificación semántica. Sin embargo, la aplicación de controles semánticos filtró el 80 % de las sugerencias no compilables. La tasa de adopción para completar una sola línea mejoró 1,9 veces en las primeras seis semanas del lanzamiento de la función, presumiblemente debido a una mayor confianza del usuario. En comparación, solo vimos un aumento de 1,3 veces en la aceptación de los idiomas en los que no agregamos una verificación semántica.
![]() |
Los servidores de idiomas con acceso al código fuente y al backend de ML están ubicados en la nube. Ambos realizan la validación semántica de las sugerencias de finalización de ML. |
Resultados
Hemos medido una tasa de adopción de usuarios del 25-34 % entre más de 10 000 desarrolladores internos de Google que utilizan la configuración de finalización en su IDE. Descubrimos que la finalización del código de aprendizaje automático semántico híbrido basado en transformador completa >3 % del código y reduce el tiempo de iteración de codificación para los Googlers en un 6 % (con un nivel de confianza del 90 %). La magnitud del cambio corresponde a los efectos típicos observados para las funciones de transformación (p. ej., marco clave) que generalmente solo afectan a una subpoblación, mientras que ML tiene el potencial de generalizarse a los lenguajes e ingenieros más importantes.
Fracción de todo el código agregado por ML | 2,6% |
Reducción del tiempo de iteración de codificación. | 6% |
Reducir el número de cambios de contexto | 7% |
Tasa de aceptación (para sugerencias visibles durante más de 750 ms) | 25% |
Promedio de caracteres por aceptación | 21 |
Métricas clave para completar el código de una sola línea medido en producción para más de 10 000 desarrolladores internos que lo utilizan en su desarrollo diario en ocho idiomas. |
Fracción de todo el código agregado por ML (con >1 línea en la sugerencia) | 0,6% |
Promedio de caracteres por aceptación | 73 |
Tasa de aceptación (para sugerencias visibles durante más de 750 ms) | 34% |
Métricas clave de finalización de código de varias líneas medidas en producción para más de 5000 desarrolladores internos de Google que lo utilizan en su desarrollo diario en ocho idiomas. |
Proporcionar terminaciones largas al explorar las API
También integramos estrechamente la finalización semántica con la finalización completa de filas. Cuando aparece el menú desplegable de finalizaciones semánticas de token único, mostramos las finalizaciones de una sola línea devueltas por el modelo ML en línea. Estos últimos representan una continuación del elemento que tiene el foco de la lista desplegable. Por ejemplo, cuando un usuario busca posibles métodos de una API, las finalizaciones de línea completa en línea muestran la llamada de método completa, que también incluye todos los parámetros de la llamada.
![]() |
Completaciones de filas completas incorporadas a través de ML, continuando con la finalización del menú desplegable semántico de enfoque. |
![]() |
Sugerencias para completaciones multilínea por ML. |
Conclusión y trabajo futuro
Demostramos cómo se puede usar la combinación de motores semánticos basados en reglas y modelos de lenguaje extenso para mejorar significativamente la productividad de los desarrolladores a través de una mejor finalización del código. Como siguiente paso, queremos aprovechar aún más los SE proporcionando información adicional para los modelos de ML en el momento de la inferencia. Un ejemplo puede ser que las predicciones largas van y vienen entre el ML y el SE, con el SE comprobando la corrección de forma iterativa y ofreciendo todas las posibles continuaciones del modelo ML. Cuando agregamos nuevas funciones impulsadas por ML, queremos tener cuidado no solo para producir resultados «inteligentes», sino también para garantizar un impacto positivo en la productividad.
Gracias
Esta investigación es el resultado de una colaboración de dos años entre Google Core y Google Research, Brain Team. Un agradecimiento especial a Marc Rasi, Yurun Shen, Vlad Pchelin, Charles Sutton, Varun Godbole, Jacob Austin, Danny Tarlow, Benjamin Lee, Satish Chandra, Ksenia Korovina, Stanislav Pyatykh, Cristopher Claeys, Petros Maniatis, Evgeny Gryaznov, Pavel Sychev, Chris Gorgolewski , Kristof Molnar, Alberto Elizondo, Ambar Murillo, Dominik Schulz, David Tattersall, Rishabh Singh, Manzil Zaheer, Ted Ying, Juanjo Carin, Alexander Froemmgen y Marcus Revaj por sus contribuciones.