Evalml: Tiempos de espera de la prueba unitaria (inestabilidad de Dask)

Creado en 9 jun. 2021  ·  11Comentarios  ·  Fuente: alteryx/evalml

Actualmente estamos viendo que las pruebas unitarias llegan al límite de acciones de GH de 6 horas. Esto no es bueno por razones obvias.

3.8 core deps 6 horas de tiempo de espera (en curso)
build_conda_pkg, 3.8 deps core, 3.7 deps no core 6 horas de tiempo de espera (en curso)
3.7 deps no centrales tiempo de espera de 6 horas
3.8 tiempo de espera de 6 horas de
3,7 deps no básicos 1,5 horas
build_conda_pkg
3.7 deps complementarios
3.8

blocker bug testing

Comentario más útil

Ahora veo el siguiente seguimiento de pila en build_conda_pkg

[gw3] linux -- Python 3.7.10 $PREFIX/bin/python

X_y_binary_cls = (          0         1         2   ...        17        18        19
0  -0.039268  0.131912 -0.211206  ...  1.976989  ...ns], 0     0
1     0
2     1
3     1
4     1
     ..
95    1
96    1
97    1
98    1
99    0
Length: 100, dtype: int64)
cluster = LocalCluster(15c4b3ad, 'tcp://127.0.0.1:45201', workers=0, threads=0, memory=0 B)

    def test_submit_training_jobs_multiple(X_y_binary_cls, cluster):
        """Test that training multiple pipelines using the parallel engine produces the
        same results as the sequential engine."""
        X, y = X_y_binary_cls
        with Client(cluster) as client:
            pipelines = [
                BinaryClassificationPipeline(
                    component_graph=["Logistic Regression Classifier"],
                    parameters={"Logistic Regression Classifier": {"n_jobs": 1}},
                ),
                BinaryClassificationPipeline(component_graph=["Baseline Classifier"]),
                BinaryClassificationPipeline(component_graph=["SVM Classifier"]),
            ]

            def fit_pipelines(pipelines, engine):
                futures = []
                for pipeline in pipelines:
                    futures.append(
                        engine.submit_training_job(
                            X=X, y=y, automl_config=automl_data, pipeline=pipeline
                        )
                    )
                results = [f.get_result() for f in futures]
                return results

            # Verify all pipelines are trained and fitted.
            seq_pipelines = fit_pipelines(pipelines, SequentialEngine())
            for pipeline in seq_pipelines:
                assert pipeline._is_fitted

            # Verify all pipelines are trained and fitted.
>           par_pipelines = fit_pipelines(pipelines, DaskEngine(client=client))

evalml/tests/automl_tests/dask_tests/test_dask_engine.py:103: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
evalml/tests/automl_tests/dask_tests/test_dask_engine.py:94: in fit_pipelines
    results = [f.get_result() for f in futures]
evalml/tests/automl_tests/dask_tests/test_dask_engine.py:94: in <listcomp>
    results = [f.get_result() for f in futures]
evalml/automl/engine/dask_engine.py:30: in get_result
    return self.work.result()
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <Future: cancelled, key: train_pipeline-4bd4a99325cd3cc91144f86b64d6503c>
timeout = None

    def result(self, timeout=None):
        """Wait until computation completes, gather result to local process.

        If *timeout* seconds are elapsed before returning, a
        ``dask.distributed.TimeoutError`` is raised.
        """
        if self.client.asynchronous:
            return self.client.sync(self._result, callback_timeout=timeout)

        # shorten error traceback
        result = self.client.sync(self._result, callback_timeout=timeout, raiseit=False)
        if self.status == "error":
            typ, exc, tb = result
            raise exc.with_traceback(tb)
        elif self.status == "cancelled":
>           raise result
E           concurrent.futures._base.CancelledError: train_pipeline-4bd4a99325cd3cc91144f86b64d6503c

Este parece ser un problema conocido en dask https://github.com/dask/distributed/issues/4612

Todos 11 comentarios

Simplemente agregando algunos datos de este departamento de 3.8 núcleos, se ejecutan una serie de verificaciones. Añadiendo los registros de esa ejecución.

github_unittests.txt

Creo que una cosa que he notado es que todos están haciendo una pausa alrededor de la marca de 91-93% completado. Dudo que tenga algún valor averiguar qué pruebas son, pero esa podría ser una ruta a seguir.

Aquí hay otro para 3.9 deps no centrales.

github_unittests_2.txt

Gracias por presentar @chukarsten

Afortunadamente, podemos descartar conda como una causa, ya que esto sucede para nuestras compilaciones de prueba unitarias normales y no solo para build_conda_pkg

¿Hay alguna otra información que debamos recopilar que pueda ayudarnos a resolver esto? Algunas ideas a continuación

  • ¿Con qué fiabilidad podemos reproducir el tiempo de espera? ¿Está sucediendo el 50% del tiempo que ejecutamos un trabajo de prueba unitaria, más, menos?
  • ¿Qué prueba o pruebas no se completan correctamente? Si podemos hacer que pytest registre el inicio y el final de cada prueba, podemos mirar los registros y deducir qué prueba no ha terminado cuando ocurre el bloqueo. Esto parecía potencialmente útil.
  • ¿Seguimos viendo estos tiempos de espera si ejecutamos pruebas sin ninguna paralelización de pytest?
  • Esto es solo una corazonada, pero ¿qué sucede si deshabilitamos las pruebas del motor dask? Sé que hemos visto algunos copos con esos recientemente # 2341
  • ¿Cómo se ve la utilización de la CPU y la memoria mientras se ejecutan las pruebas?

( @freddyaboulton te agregué aquí ya que esto se conecta a # 2298 y # 1815)

Cambiando el Makefile para hacer un registro detallado con pytest, obtenemos el siguiente registro
. Esto muestra que la última prueba ejecutada es "evalml / tuners / random_search_tuner.py :: evalml.tuners.random_search_tuner.RandomSearchTuner"

Creo que @freddyaboulton ciertamente tiene algo aquí y estamos apuntando firmemente a Dask. Hice este PR para separar las pruebas unitarias de dask. Creo que tenemos la opción de no evitar la fusión si fallan. Este PR falló en test_automl_immediate_quit, que todavía está en la matriz de pruebas de dask.

Es desconcertante investigar la causa raíz de las fallas en las pruebas unitarias de dask. Los registros generan mucho de esto:

distributed.worker - WARNING - Could not find data: {'Series-32a3ef2ca4739b46a6acc2ac58638b32': ['tcp://127.0.0.1:45587']} on workers: [] (who_has: {'Series-32a3ef2ca4739b46a6acc2ac58638b32': ['tcp://127.0.0.1:45587']})
distributed.scheduler - WARNING - Communication failed during replication: {'status': 'missing-data', 'keys'

¿Por qué pasó esto? Bueno, parece que dondequiera que los datos sobre los que se actúa está perdiendo referencia a esos datos. Además, 'trabajadores: []' sugiere que quizás el proceso de niñera está matando a los trabajadores. Sospecho que algo está sucediendo con la forma en que se dispersan los datos, pero también sospecho de lo que está sucediendo bajo las sábanas con estos cuatro trabajos ejecutándose juntos en pseudo paralelo / serie.

Este problema distribuido de dask sugiere deshabilitar el escalado adaptativo para el clúster. Desafortunadamente, no usamos clústeres adaptativos, solo clústeres estáticos locales regulares, por lo que ese no es el problema. Este problema apunta a la dispersión de los datos como la causa potencial del problema, donde los trabajadores están siendo abandonados, pero no tenemos los mismos errores de conexión.

Después de intentar # 2376 para separar los trabajos de dask y configurar broadcast=False para el cliente de DaskEngine, de forma predeterminada, tengo un error de prueba inestable con test_automl_immediate_quit. Documentado aquí .

Ahora veo el siguiente seguimiento de pila en build_conda_pkg

[gw3] linux -- Python 3.7.10 $PREFIX/bin/python

X_y_binary_cls = (          0         1         2   ...        17        18        19
0  -0.039268  0.131912 -0.211206  ...  1.976989  ...ns], 0     0
1     0
2     1
3     1
4     1
     ..
95    1
96    1
97    1
98    1
99    0
Length: 100, dtype: int64)
cluster = LocalCluster(15c4b3ad, 'tcp://127.0.0.1:45201', workers=0, threads=0, memory=0 B)

    def test_submit_training_jobs_multiple(X_y_binary_cls, cluster):
        """Test that training multiple pipelines using the parallel engine produces the
        same results as the sequential engine."""
        X, y = X_y_binary_cls
        with Client(cluster) as client:
            pipelines = [
                BinaryClassificationPipeline(
                    component_graph=["Logistic Regression Classifier"],
                    parameters={"Logistic Regression Classifier": {"n_jobs": 1}},
                ),
                BinaryClassificationPipeline(component_graph=["Baseline Classifier"]),
                BinaryClassificationPipeline(component_graph=["SVM Classifier"]),
            ]

            def fit_pipelines(pipelines, engine):
                futures = []
                for pipeline in pipelines:
                    futures.append(
                        engine.submit_training_job(
                            X=X, y=y, automl_config=automl_data, pipeline=pipeline
                        )
                    )
                results = [f.get_result() for f in futures]
                return results

            # Verify all pipelines are trained and fitted.
            seq_pipelines = fit_pipelines(pipelines, SequentialEngine())
            for pipeline in seq_pipelines:
                assert pipeline._is_fitted

            # Verify all pipelines are trained and fitted.
>           par_pipelines = fit_pipelines(pipelines, DaskEngine(client=client))

evalml/tests/automl_tests/dask_tests/test_dask_engine.py:103: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
evalml/tests/automl_tests/dask_tests/test_dask_engine.py:94: in fit_pipelines
    results = [f.get_result() for f in futures]
evalml/tests/automl_tests/dask_tests/test_dask_engine.py:94: in <listcomp>
    results = [f.get_result() for f in futures]
evalml/automl/engine/dask_engine.py:30: in get_result
    return self.work.result()
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <Future: cancelled, key: train_pipeline-4bd4a99325cd3cc91144f86b64d6503c>
timeout = None

    def result(self, timeout=None):
        """Wait until computation completes, gather result to local process.

        If *timeout* seconds are elapsed before returning, a
        ``dask.distributed.TimeoutError`` is raised.
        """
        if self.client.asynchronous:
            return self.client.sync(self._result, callback_timeout=timeout)

        # shorten error traceback
        result = self.client.sync(self._result, callback_timeout=timeout, raiseit=False)
        if self.status == "error":
            typ, exc, tb = result
            raise exc.with_traceback(tb)
        elif self.status == "cancelled":
>           raise result
E           concurrent.futures._base.CancelledError: train_pipeline-4bd4a99325cd3cc91144f86b64d6503c

Este parece ser un problema conocido en dask https://github.com/dask/distributed/issues/4612

Eliminé mi publicación anterior, pero aquí hay una roja: https://github.com/alteryx/evalml/actions/runs/939673304 , parece ser el mismo seguimiento de pila que @freddyaboulton publicado anteriormente.

Creo que este problema ya no se bloquea por [este PR] para separar los trabajos de dask (https://github.com/alteryx/evalml/pull/2376), este PR para refactorizar los trabajos de dask para reducir las escamas, y este PR para hacer que los trabajos de dask separados no se bloqueen para fusionarse con el principal y este PR para agregar un tiempo de espera para evitar que las pruebas de dask patológicas demoren 6 horas para finalmente ser canceladas por GH Actions.

Voy a mover esto a cerrado porque los tiempos de espera relacionados con dask ya no son un problema y no deberían serlo en el futuro previsible. Sin embargo, aún se desconoce la causa subyacente.

¿Fue útil esta página
0 / 5 - 0 calificaciones