Curso / TensorFlow / TensorFlow vs. PyTorch
● piloto de formato · comparativo

TensorFlow · Referencia comparativa (única página)

TensorFlow, en contraste con lo que ya sabés de PyTorch

Esta es la única página de TensorFlow del curso, a propósito: no para aprenderlo con la misma profundidad, sino para poder leer código TF/Keras cuando aparezca (y va a aparecer) sin sentirte perdido, entendiendo qué decisión de diseño reemplaza a cuál de PyTorch.

1. tf.Tensor es inmutable; tf.Variable existe para lo que cambia

Primera diferencia real de diseño: en PyTorch, un mismo torch.Tensor puede ser constante o mutable (con requires_grad y operaciones in-place). En TensorFlow, la distinción es de tipo, no de flag: tf.Tensor es inmutable siempre — para pesos de modelo o cualquier estado que cambie con el tiempo, hace falta tf.Variable:

x = tf.constant([[1., 2., 3.]])   # inmutable

var = tf.Variable([0.0, 0.0, 0.0])
var.assign([1, 2, 3])          # mutación explícita, método propio
var.assign_add([1, 1, 1])      # → [2, 3, 4]

Ni mejor ni peor que la unificación de PyTorch (un solo tipo Tensor para todo) — es simplemente una decisión de diseño distinta: separar el tipo por su mutabilidad en vez de por un flag sobre el mismo tipo.

2. GradientTape: autograd explícito, no implícito

En PyTorch, cualquier operación sobre un tensor con requires_grad=True queda registrada automáticamente en el grafo. TensorFlow lo hace explícito con un context manager:

x = tf.Variable(1.0)

with tf.GradientTape() as tape:
    y = x**2 + 2*x - 5

g_x = tape.gradient(y, x)   # dy/dx = 2x + 2 → 4.0 en x=1

Es el mismo concepto que loss.backward() de PyTorch (regla de la cadena sobre un grafo registrado durante el forward), con una diferencia de superficie: en TF, el alcance de qué se registra está delimitado explícitamente por el bloque with; en PyTorch, está delimitado por el flag requires_grad de cada tensor. Misma idea, distinto mecanismo de scoping.

3. tf.function: el grafo estático, pero opcional

Acá está la diferencia histórica más importante, ya suavizada. El decorador @tf.function convierte una función Python en un grafo TensorFlow optimizado la primera vez que se ejecuta ("tracing"), y las llamadas siguientes reusan ese grafo compilado en vez de reinterpretar Python:

@tf.function
def my_func(x):
    print('Tracing.')   # solo se imprime la PRIMERA vez con cada shape/dtype nuevo
    return tf.reduce_sum(x)

Es, conceptualmente, el ancestro directo de torch.compile — capturar un grafo optimizado a partir de código Python normal. La diferencia clave es cuándo se volvió opcional: en TensorFlow, tf.function existe desde TF 2.0 (2019) como una optimización que herés que agregar a mano, función por función. En PyTorch, torch.compile llegó después (2023) pero se aplicó de forma mucho más transparente sobre modelos ya escritos en estilo eager puro, sin decorar nada dentro del modelo mismo.

4. Keras: la razón por la que TensorFlow sigue siendo fácil

Keras es la capa de alto nivel que hace que gran parte del código TensorFlow real se vea casi como pseudocódigo:

model = tf.keras.Sequential([
    tf.keras.layers.Flatten(input_shape=(28, 28)),
    tf.keras.layers.Dense(128, activation='relu'),
    tf.keras.layers.Dropout(0.2),
    tf.keras.layers.Dense(10)
])

model.compile(optimizer='adam', loss=loss_fn, metrics=['accuracy'])
model.fit(x_train, y_train, epochs=5)
model.evaluate(x_test, y_test)

No hay training loop manual acá — compile() + fit() lo esconden por completo. Es el equivalente funcional de Learner.fit() en fastai/miniai o del Trainer de HF: una capa de alto nivel sobre el mismo mecanismo de gradientes que viste expuesto a mano en la página de autograd de PyTorch. La guía oficial de TensorFlow, de hecho, primero muestra el loop manual con GradientTape (para enseñar el mecanismo) y después dice explícitamente: "considerá usar Model.compile/Model.fit antes de escribir el tuyo propio" — el mismo consejo pedagógico que fast.ai dio en su Lección 5 sobre usar un framework en vez de reescribir SGD a mano en cada proyecto.

5. Por qué la industria migró a PyTorch — con honestidad

No es una superioridad técnica absoluta — es una combinación de factores que se reforzaron entre sí:

  • Ergonomía de investigación primero: eager por defecto desde el día uno, sin necesitar sesiones ni placeholders para debuggear.
  • Efecto de red académico: código de referencia de papers en PyTorch → más reproducciones, más tutoriales, más gente formada.
  • Consolidación del ecosistema de LLMs: Hugging Face Transformers, que se convirtió en el estándar de facto para modelos preentrenados, eligió PyTorch como backend principal — y ese ecosistema arrastró todo lo que se construyó alrededor (vLLM, PEFT, TRL, Diffusers).
  • TensorFlow 1.x llegó tarde a la ergonomía eager: para cuando TF 2.0 la adoptó (2019), la comunidad de investigación ya había migrado en gran parte.

6. Dónde TensorFlow (y JAX) siguen importando

🟢 No es una historia de "TensorFlow perdió"

TensorFlow sigue siendo dominante en producción móvil y embebida (TensorFlow Lite), despliegue web (TensorFlow.js) y en infraestructura de serving madura (TensorFlow Serving), donde la exportación a SavedModel —un grafo autocontenido, independiente del código Python que lo creó— sigue siendo una ventaja operativa real frente al ecosistema de PyTorch, más fragmentado en esa capa (TorchScript, ONNX, ExecuTorch, cada uno con su propio nicho). Y el dato más interesante para 2026: buena parte de la investigación de frontera de Google DeepMind (Gemini incluido) hoy corre sobre JAX, no sobre TensorFlow clásico — un framework distinto, con compilación XLA nativa y un estilo funcional muy alejado tanto de TensorFlow como de PyTorch. El propio Google, en investigación, se movió lejos de TensorFlow — no hacia PyTorch, sino hacia una tercera opción.

7. Resumen — cierre del curso completo

  1. tf.Tensor inmutable + tf.Variable mutable reemplaza al tensor unificado de PyTorch — misma capacidad, tipos separados.
  2. GradientTape es autograd con scoping explícito por bloque, en vez de por flag requires_grad en cada tensor.
  3. tf.function es el ancestro conceptual de torch.compile — la diferencia real es que PyTorch logró hacerlo transparente años después.
  4. Keras (compile+fit) es al training loop de TF lo que Learner.fit()/Trainer son en PyTorch — una capa de conveniencia sobre el mismo mecanismo de gradientes.
  5. La migración a PyTorch fue ergonomía + efecto de red + el ecosistema de HF, no una sola causa técnica — y TensorFlow sigue siendo relevante en mobile/web/serving, mientras la investigación de Google ya se movió a JAX, un tercer framework.

Con esto se cierra el recorrido completo del curso: Hugging Face LLM Course (12 capítulos), fast.ai (Parte 1 + Parte 2), PyTorch y TensorFlow — de la atención en un transformer al framework que hace correr todo lo demás por debajo.