Install any skill in seconds. Free to start, no credit card required.
Get Started Free →TensorFlow best practices for tf.function, GPU memory, and deployment
.claude/skills/brycewang-stanford-tensorflow-guide/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-07 | ✗→✓ | ▲ Improved | 128% | 0% |
| case-01 | ✓→✓ | = Same ✓ | 104% | 0% |
| case-02 | ✓→✓ | = Same ✓ | 84% | 0% |
| case-03 | ✓→✓ | = Same ✓ | 111% | 0% |
| case-04 | ✓→✓ | = Same ✓ | 141% | 0% |
TensorFlow is a production-grade machine learning framework that excels at deployment, distributed training, and hardware acceleration. While PyTorch dominates pure research prototyping, TensorFlow remains the standard in industry ML systems and is heavily used in applied research where models must move from experiment to production.
TensorFlow 2.x unified eager execution with graph-mode performance through tf.function, but this hybrid approach introduces subtle pitfalls. Understanding when and how TensorFlow traces functions, manages GPU memory, and distributes computation is essential for writing correct and efficient code.
This guide covers the key patterns that trip up researchers: tf.function tracing semantics, GPU memory management, distributed strategies, model export, and the ecosystem of tools (TFX, TensorBoard, TF Serving) that make TensorFlow uniquely powerful for end-to-end ML workflows.
pythonimport tensorflow as tf @tf.function def add(a, b): print("Tracing!") # Runs only during tracing, NOT every call tf.print("Executing!") # Runs every call (TF op) return a + b # First call with float32 shape (2,) -- traces add(tf.constant([1.0, 2.0]), tf.constant([3.0, 4.0])) # Prints "Tracing!" + "Executing!" # Second call with same signature -- reuses trace add(tf.constant([5.0, 6.0]), tf.constant([7.0, 8.0])) # Prints only "Executing!" # Third call with different dtype -- re-traces! add(tf.constant([1, 2]), tf.constant([3, 4])) # Prints "Tracing!" + "Executing!"
python# PITFALL 1: Python side effects in tf.function counter = 0 @tf.function def increment(): global counter counter += 1 # Only runs during tracing! counter stays at 1 forever. return counter # FIX: Use tf.Variable for mutable state counter = tf.Variable(0) @tf.function def increment(): counter.assign_add(1) return counter # PITFALL 2: Creating variables inside tf.function @tf.function def bad_function(x): w = tf.Variable(tf.random.normal([3, 3])) # ERROR on second call! return x @ w # FIX: Create variables outside, pass as arguments or use Keras layers w = tf.Variable(tf.random.normal([3, 3])) @tf.function def good_function(x): return x @ w # PITFALL 3: Python lists that grow @tf.function def bad_accumulate(dataset): results = [] for x in dataset: results.append(x * 2) # Creates new trace on every iteration! return results # FIX: Use tf.TensorArray @tf.function def good_accumulate(dataset): results = tf.TensorArray(tf.float32, size=0, dynamic_size=True) for i, x in enumerate(dataset): results = results.write(i, x * 2) return results.stack()
python@tf.function(input_signature=[ tf.TensorSpec(shape=[None, 224, 224, 3], dtype=tf.float32), tf.TensorSpec(shape=[None], dtype=tf.int64), ]) def train_step(images, labels): """Fixed signature prevents re-tracing on different batch sizes.""" with tf.GradientTape() as tape: predictions = model(images, training=True) loss = loss_fn(labels, predictions) gradients = tape.gradient(loss, model.trainable_variables) optimizer.apply_gradients(zip(gradients, model.trainable_variables)) return loss
python# Problem: TensorFlow grabs ALL GPU memory by default # Solution: Enable memory growth gpus = tf.config.list_physical_devices("GPU") for gpu in gpus: tf.config.experimental.set_memory_growth(gpu, True) # Alternative: Set a hard memory limit tf.config.set_logical_device_configuration( gpus[0], [tf.config.LogicalDeviceConfiguration(memory_limit=8192)] # 8 GB ) # Monitor memory usage print(tf.config.experimental.get_memory_info("GPU:0"))
| Strategy | GPUs | Machines | Sync | Use Case | |----------|------|----------|------|----------| | MirroredStrategy | Multiple | 1 | Sync | Most common multi-GPU | | MultiWorkerMirroredStrategy | Multiple | Multiple | Sync | Multi-node training | | TPUStrategy | TPU cores | 1 pod | Sync | TPU training | | ParameterServerStrategy | Multiple | Multiple | Async | Very large models |
python# Multi-GPU training with MirroredStrategy strategy = tf.distribute.MirroredStrategy() print(f"Number of devices: {strategy.num_replicas_in_sync}") with strategy.scope(): model = build_model() model.compile( optimizer=tf.keras.optimizers.Adam(learning_rate=0.001 * strategy.num_replicas_in_sync), loss="sparse_categorical_crossentropy", metrics=["accuracy"], ) # Global batch size = per_replica_batch * num_replicas global_batch_size = 32 * strategy.num_replicas_in_sync dataset = dataset.batch(global_batch_size) model.fit(dataset, epochs=10)
python# SavedModel: The universal export format model.save("saved_model/my_model") # Load with full TF capabilities loaded = tf.saved_model.load("saved_model/my_model") infer = loaded.signatures["serving_default"] # TF Lite for mobile/edge deployment converter = tf.lite.TFLiteConverter.from_saved_model("saved_model/my_model") converter.optimizations = [tf.lite.Optimize.DEFAULT] tflite_model = converter.convert() with open("model.tflite", "wb") as f: f.write(tflite_model) # TensorFlow.js for browser deployment # Command line: # tensorflowjs_converter --input_format=tf_saved_model saved_model/my_model web_model/
python# XLA (Accelerated Linear Algebra) compiles tf.functions for hardware @tf.function(jit_compile=True) def fast_matmul(a, b): return tf.matmul(a, b) # Enable XLA globally for Keras tf.config.optimizer.set_jit(True) # Benchmark XLA vs non-XLA import time a = tf.random.normal([1024, 1024]) b = tf.random.normal([1024, 1024]) # Warm up fast_matmul(a, b) start = time.time() for _ in range(1000): fast_matmul(a, b) print(f"XLA matmul: {time.time() - start:.3f}s")
python# Enable eager mode for debugging tf.config.run_functions_eagerly(True) # TensorBoard profiler integration log_dir = "logs/profile" tf.profiler.experimental.start(log_dir) # ... run training steps ... tf.profiler.experimental.stop() # View: tensorboard --logdir logs/profile # Check for numerical issues tf.debugging.enable_check_numerics() # Raises on NaN/Inf
tf.function with explicit input_signature to prevent re-tracing in production.tf.function unless you use tf.cond / tf.while_loop.tf.keras.mixed_precision.set_global_policy("mixed_float16") for modern GPUs.| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-01 | pass→pass | 13,463 | 16,422 | +22% | 1 | 1 | 0% | 2,286 | 4,672 | +104% | 0 | 0 | — |
case-02 | pass→pass | 14,112 | 13,514 | -4% | 1 | 1 | 0% | 2,378 | 4,371 | +84% | 0 | 0 | — |
case-03 | pass→pass | 13,792 | 14,344 | +4% | 1 | 1 | 0% | 2,124 | 4,473 | +111% | 0 | 0 | — |
case-04 | pass→pass | 10,837 | 12,958 | +20% | 1 | 1 | 0% | 1,890 | 4,556 | +141% | 0 | 0 | — |
case-17 | pass→pass | 10,457 | 9,754 | -7% | 1 | 1 | 0% | 1,769 | 3,782 | +114% | 0 | 0 | — |
case-05 | pass→pass | 8,447 | 5,135 | -39% | 1 | 1 | 0% | 1,287 | 3,029 | +135% | 0 | 0 | — |
case-06 | pass→pass | 8,314 | 4,390 | -47% | 1 | 1 | 0% | 1,143 | 2,803 | +145% | 0 | 0 | — |
case-07 | fail→pass | 9,977 | 8,130 | -19% | 1 | 1 | 0% | 1,654 | 3,768 | +128% | 0 | 0 | — |
case-08 | pass→pass | 7,444 | 7,034 | -6% | 1 | 1 | 0% | 1,073 | 3,460 | +222% | 0 | 0 | — |
case-09 | pass→pass | 8,161 | 5,915 | -28% | 1 | 1 | 0% | 1,332 | 3,105 | +133% | 0 | 0 | — |
case-10 | pass→pass | 5,212 | 6,807 | +31% | 1 | 1 | 0% | 955 | 2,840 | +197% | 0 | 0 | — |
case-11 | pass→pass | 3,110 | 16,280 | +423% | 1 | 1 | 0% | 534 | 2,690 | +404% | 0 | 0 | — |
case-12 | pass→pass | 9,047 | 3,425 | -62% | 1 | 1 | 0% | 1,687 | 2,750 | +63% | 0 | 0 | — |
case-18 | pass→pass | 10,810 | 17,561 | +62% | 1 | 1 | 0% | 1,999 | 4,860 | +143% | 0 | 0 | — |
case-13 | pass→pass | 5,109 | 5,093 | -0% | 1 | 1 | 0% | 860 | 2,998 | +249% | 0 | 0 | — |
case-14 | pass→pass | 5,104 | 4,345 | -15% | 1 | 1 | 0% | 822 | 2,894 | +252% | 0 | 0 | — |
case-15 | pass→pass | 4,337 | 2,847 | -34% | 1 | 1 | 0% | 698 | 2,694 | +286% | 0 | 0 | — |
case-16 | pass→pass | 3,630 | 3,938 | +8% | 1 | 1 | 0% | 572 | 2,840 | +397% | 0 | 0 | — |
case-19 | pass→pass | 5,193 | 3,856 | -26% | 1 | 1 | 0% | 912 | 2,853 | +213% | 0 | 0 | — |
case-20 | pass→pass | 13,134 | 13,510 | +3% | 1 | 1 | 0% | 2,247 | 4,473 | +99% | 0 | 0 | — |
case-21 | pass→pass | 13,821 | 3,229 | -77% | 1 | 1 | 0% | 2,142 | 2,660 | +24% | 0 | 0 | — |
case-22 | pass→pass | 19,113 | 21,061 | +10% | 1 | 1 | 0% | 3,791 | 6,286 | +66% | 0 | 0 | — |
case-23 | pass→pass | 3,592 | 4,830 | +34% | 1 | 1 | 0% | 549 | 3,063 | +458% | 0 | 0 | — |
case-24 | pass→pass | 12,067 | 9,632 | -20% | 1 | 1 | 0% | 1,946 | 3,893 | +100% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 24 cases were attempted. The headline lift of +4 percentage points is the difference between those two pass rates over the 24 comparable cases.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
Other measured skills in the registry, with their headline benchmark lift.