ความแม่นยำแบบผสม

ดูบน TensorFlow.org ทำงานใน Google Colab ดูแหล่งที่มาบน GitHub ดาวน์โหลดโน๊ตบุ๊ค

ภาพรวม

ความแม่นยำแบบผสมคือการใช้ทศนิยมทั้งแบบ 16 บิตและ 32 บิตในแบบจำลองระหว่างการฝึกเพื่อให้ทำงานเร็วขึ้นและใช้หน่วยความจำน้อยลง โดยการรักษาบางส่วนของโมเดลไว้ในประเภท 32 บิตเพื่อความเสถียรของตัวเลข โมเดลจะมีเวลาขั้นตอนที่ต่ำกว่าและฝึกฝนเท่าๆ กัน ในแง่ของเมตริกการประเมิน เช่น ความแม่นยำ คู่มือนี้อธิบายวิธีใช้ API ความแม่นยำแบบผสมของ Keras เพื่อเพิ่มความเร็วโมเดลของคุณ การใช้ API นี้สามารถปรับปรุงประสิทธิภาพได้มากกว่า 3 เท่าใน GPU สมัยใหม่และ 60% สำหรับ TPU

ทุกวันนี้ โมเดลส่วนใหญ่ใช้ float32 dtype ซึ่งใช้หน่วยความจำ 32 บิต อย่างไรก็ตาม มี dtypes ที่มีความแม่นยำต่ำกว่าสองแบบคือ float16 และ bfloat16 ซึ่งแต่ละอันใช้หน่วยความจำ 16 บิตแทน ตัวเร่งความเร็วสมัยใหม่สามารถเรียกใช้การดำเนินการได้เร็วขึ้นใน dtypes 16 บิต เนื่องจากมีฮาร์ดแวร์เฉพาะสำหรับรันการคำนวณ 16 บิต และ dtypes 16 บิตสามารถอ่านจากหน่วยความจำได้เร็วขึ้น

GPU NVIDIA สามารถเรียกใช้การดำเนินการใน float16 ได้เร็วกว่าใน float32 และ TPU สามารถเรียกใช้การดำเนินการใน bfloat16 ได้เร็วกว่า float32 ดังนั้น dtypes ที่มีความแม่นยำต่ำกว่าเหล่านี้จึงควรใช้เมื่อทำได้บนอุปกรณ์เหล่านั้น อย่างไรก็ตาม ตัวแปรและการคำนวณบางส่วนควรยังคงอยู่ใน float32 ด้วยเหตุผลที่เป็นตัวเลข เพื่อให้โมเดลฝึกคุณภาพเดียวกัน API ความแม่นยำแบบผสมของ Keras ช่วยให้คุณใช้การผสมผสานระหว่าง float16 หรือ bfloat16 กับ float32 เพื่อรับประโยชน์ด้านประสิทธิภาพจาก float16/bfloat16 และความเสถียรของตัวเลขจาก float32

ติดตั้ง

import tensorflow as tf

from tensorflow import keras
from tensorflow.keras import layers
from tensorflow.keras import mixed_precision

ฮาร์ดแวร์ที่รองรับ

แม้ว่าความแม่นยำแบบผสมจะทำงานบนฮาร์ดแวร์ส่วนใหญ่ แต่จะเพิ่มความเร็วให้กับโมเดลใน NVIDIA GPUs และ Cloud TPU ล่าสุดเท่านั้น NVIDIA GPUs รองรับการใช้ float16 และ float32 ผสมกัน ในขณะที่ TPU รองรับการผสมผสานระหว่าง bfloat16 และ float32

ในบรรดา GPU ของ NVIDIA ผู้ที่มีความสามารถในการคำนวณ 7.0 หรือสูงกว่าจะได้รับประโยชน์ด้านประสิทธิภาพสูงสุดจากความแม่นยำแบบผสม เนื่องจากมีหน่วยฮาร์ดแวร์พิเศษที่เรียกว่า Tensor Cores เพื่อเร่งการคูณเมทริกซ์โฟลต16และการบิดเบี้ยว GPU รุ่นเก่าไม่มีประโยชน์ด้านประสิทธิภาพทางคณิตศาสตร์สำหรับการใช้ความแม่นยำแบบผสม อย่างไรก็ตาม การประหยัดหน่วยความจำและแบนด์วิดท์สามารถช่วยเพิ่มความเร็วได้บางส่วน คุณสามารถค้นหาความสามารถในการประมวลผลสำหรับ GPU ของคุณได้ที่ หน้าเว็บ CUDA GPU ของ NVIDIA ตัวอย่างของ GPU ที่จะได้รับประโยชน์สูงสุดจากความแม่นยำแบบผสม ได้แก่ RTX GPU, V100 และ A100

คุณสามารถตรวจสอบประเภท GPU ของคุณได้ดังต่อไปนี้ คำสั่งจะมีอยู่ก็ต่อเมื่อมีการติดตั้งไดรเวอร์ NVIDIA ดังนั้นสิ่งต่อไปนี้จะทำให้เกิดข้อผิดพลาด

nvidia-smi -L
GPU 0: Tesla V100-SXM2-16GB (UUID: GPU-99e10c4d-de77-42ee-4524-6c41c4e5e47d)

Cloud TPU ทั้งหมดรองรับ bfloat16

แม้แต่ใน CPU และ GPU รุ่นเก่า ซึ่งคาดว่าจะไม่มีการเร่งความเร็ว API ที่มีความแม่นยำแบบผสมก็ยังสามารถใช้สำหรับการทดสอบหน่วย การดีบัก หรือเพียงเพื่อลองใช้ API อย่างไรก็ตาม สำหรับซีพียู ความแม่นยำแบบผสมจะทำงานช้าลงอย่างมาก

การตั้งค่านโยบาย dtype

หากต้องการใช้ความแม่นยำแบบผสมใน Keras คุณต้องสร้าง tf.keras.mixed_precision.Policy โดยทั่วไปจะเรียกว่า นโยบาย dtype นโยบาย Dtype ระบุเลเยอร์ dtypes ที่จะเรียกใช้ ในคู่มือนี้ คุณจะสร้างนโยบายจากสตริง 'mixed_float16' และตั้งเป็นนโยบายส่วนกลาง ซึ่งจะทำให้เลเยอร์ที่สร้างขึ้นในภายหลังใช้ความแม่นยำแบบผสมกับ float16 และ float32 ผสมกัน

policy = mixed_precision.Policy('mixed_float16')
mixed_precision.set_global_policy(policy)
INFO:tensorflow:Mixed precision compatibility check (mixed_float16): OK
Your GPU will likely run quickly with dtype policy mixed_float16 as it has compute capability of at least 7.0. Your GPU: Tesla V100-SXM2-16GB, compute capability 7.0

พูดง่ายๆ ก็คือ คุณสามารถส่งสตริงไปที่ set_global_policy ได้โดยตรง ซึ่งโดยทั่วไปแล้วจะทำในเชิงปฏิบัติ

# Equivalent to the two lines above
mixed_precision.set_global_policy('mixed_float16')

นโยบายระบุลักษณะสำคัญสองประการของเลเยอร์: dtype การคำนวณของเลเยอร์เสร็จสิ้น และ dtype ของตัวแปรของเลเยอร์ ด้านบน คุณได้สร้างนโยบาย mixed_float16 (เช่น mixed_precision.Policy ที่สร้างขึ้นโดยส่งสตริง 'mixed_float16' ไปยังคอนสตรัคเตอร์) ด้วยนโยบายนี้ เลเยอร์ใช้การคำนวณ float16 และตัวแปร float32 การคำนวณเสร็จสิ้นใน float16 เพื่อประสิทธิภาพ แต่ตัวแปรต้องเก็บไว้ใน float32 เพื่อความเสถียรของตัวเลข คุณสามารถสอบถามคุณสมบัติเหล่านี้ของนโยบายได้โดยตรง

print('Compute dtype: %s' % policy.compute_dtype)
print('Variable dtype: %s' % policy.variable_dtype)
Compute dtype: float16
Variable dtype: float32

ดังที่ได้กล่าวไว้ก่อนหน้านี้ นโยบาย mixed_float16 จะปรับปรุงประสิทธิภาพการทำงานบน NVIDIA GPUs ที่มีความสามารถในการประมวลผลอย่างน้อย 7.0 เป็นอย่างน้อย นโยบายจะทำงานบน GPU และ CPU อื่นๆ แต่อาจไม่ปรับปรุงประสิทธิภาพ สำหรับ TPU ควรใช้นโยบาย mixed_bfloat16 แทน

การสร้างแบบจำลอง

ต่อไป เรามาเริ่มสร้างแบบจำลองอย่างง่ายกัน โมเดลของเล่นขนาดเล็กมากมักไม่ได้รับประโยชน์จากความแม่นยำแบบผสม เนื่องจากโอเวอร์เฮดจากรันไทม์ TensorFlow มักจะครอบงำเวลาดำเนินการ ทำให้การปรับปรุงประสิทธิภาพใดๆ บน GPU นั้นไม่สำคัญ ดังนั้น เรามาสร้าง Dense Layers ขนาดใหญ่ 2 ชั้น โดยแต่ละอันมี 4096 หน่วย หากใช้ GPU

inputs = keras.Input(shape=(784,), name='digits')
if tf.config.list_physical_devices('GPU'):
  print('The model will run with 4096 units on a GPU')
  num_units = 4096
else:
  # Use fewer units on CPUs so the model finishes in a reasonable amount of time
  print('The model will run with 64 units on a CPU')
  num_units = 64
dense1 = layers.Dense(num_units, activation='relu', name='dense_1')
x = dense1(inputs)
dense2 = layers.Dense(num_units, activation='relu', name='dense_2')
x = dense2(x)
The model will run with 4096 units on a GPU

แต่ละชั้นมีนโยบายและใช้นโยบายส่วนกลางเป็นค่าเริ่มต้น ดังนั้นแต่ละเลเยอร์ Dense จึงมีนโยบาย mixed_float16 เนื่องจากคุณตั้งค่านโยบายส่วนกลางเป็น mixed_float16 ก่อนหน้านี้ สิ่งนี้จะทำให้เลเยอร์ที่หนาแน่นทำการคำนวณ float16 และมีตัวแปร float32 พวกเขาส่งอินพุตไปที่ float16 เพื่อทำการคำนวณ float16 ซึ่งทำให้ผลลัพธ์ของพวกเขาเป็น float16 เป็นผล ตัวแปรของพวกเขาคือ float32 และจะถูกส่งไปที่ float16 เมื่อเลเยอร์ถูกเรียกเพื่อหลีกเลี่ยงข้อผิดพลาดจาก dtype ไม่ตรงกัน

print(dense1.dtype_policy)
print('x.dtype: %s' % x.dtype.name)
# 'kernel' is dense1's variable
print('dense1.kernel.dtype: %s' % dense1.kernel.dtype.name)
<Policy "mixed_float16">
x.dtype: float16
dense1.kernel.dtype: float32

ถัดไป สร้างการคาดการณ์ผลลัพธ์ โดยปกติ คุณสามารถสร้างการทำนายผลลัพธ์ได้ดังนี้ แต่ตัวเลข float16 อาจไม่เสถียรเสมอไป

# INCORRECT: softmax and model output will be float16, when it should be float32
outputs = layers.Dense(10, activation='softmax', name='predictions')(x)
print('Outputs dtype: %s' % outputs.dtype.name)
Outputs dtype: float16

การเปิดใช้งาน softmax ที่ส่วนท้ายของโมเดลควรเป็น float32 เนื่องจากนโยบาย dtype เป็น mixed_float16 การเปิดใช้งาน softmax โดยปกติจะมีการคำนวณ float16 dtype และเอาต์พุต float16 เทนเซอร์

ซึ่งสามารถแก้ไขได้โดยแยกชั้น Dense และ softmax ออก และส่งผ่าน dtype='float32' ไปยังเลเยอร์ softmax:

# CORRECT: softmax and model output are float32
x = layers.Dense(10, name='dense_logits')(x)
outputs = layers.Activation('softmax', dtype='float32', name='predictions')(x)
print('Outputs dtype: %s' % outputs.dtype.name)
Outputs dtype: float32
ตัวยึดตำแหน่ง23

การส่ง dtype='float32' ไปยังตัวสร้างเลเยอร์ softmax จะแทนที่นโยบาย dtype ของเลเยอร์ให้เป็นนโยบาย float32 ซึ่งทำการคำนวณและเก็บตัวแปรไว้ใน float32 ในทำนองเดียวกัน คุณสามารถส่งผ่าน dtype=mixed_precision.Policy('float32') ; ชั้นจะแปลงอาร์กิวเมนต์ dtype เป็นนโยบายเสมอ เนื่องจากเลเยอร์ Activation ไม่มีตัวแปร dtype ตัวแปรของนโยบายจึงถูกละเว้น แต่ dtype การคำนวณของนโยบายของ float32 ทำให้ softmax และเอาต์พุตของโมเดลเป็น float32

การเพิ่ม softmax float16 ตรงกลางโมเดลนั้นใช้ได้ แต่ softmax ที่ส่วนท้ายของโมเดลควรอยู่ใน float32 เหตุผลก็คือถ้าเทนเซอร์ระดับกลางที่ไหลจาก softmax ไปสู่การสูญเสียคือ float16 หรือ bfloat16 ปัญหาด้านตัวเลขอาจเกิดขึ้น

คุณสามารถแทนที่ dtype ของเลเยอร์ใดก็ได้ให้เป็น float32 โดยผ่าน dtype='float32' หากคุณคิดว่าการคำนวณ float16 จะไม่เสถียรเชิงตัวเลข แต่โดยทั่วไป สิ่งนี้จำเป็นสำหรับเลเยอร์สุดท้ายของโมเดลเท่านั้น เนื่องจากเลเยอร์ส่วนใหญ่มีความแม่นยำเพียงพอกับ mixed_float16 และ mixed_bfloat16

แม้ว่าโมเดลจะไม่ได้ลงท้ายด้วย softmax แต่ผลลัพธ์ก็ยังควรเป็น float32 แม้ว่าจะไม่จำเป็นสำหรับโมเดลเฉพาะนี้ แต่เอาต์พุตของโมเดลสามารถแคสต์เป็น float32 ได้ดังนี้:

# The linear activation is an identity function. So this simply casts 'outputs'
# to float32. In this particular case, 'outputs' is already float32 so this is a
# no-op.
outputs = layers.Activation('linear', dtype='float32')(outputs)

ถัดไป เสร็จสิ้นและคอมไพล์โมเดล และสร้างข้อมูลอินพุต:

model = keras.Model(inputs=inputs, outputs=outputs)
model.compile(loss='sparse_categorical_crossentropy',
              optimizer=keras.optimizers.RMSprop(),
              metrics=['accuracy'])

(x_train, y_train), (x_test, y_test) = keras.datasets.mnist.load_data()
x_train = x_train.reshape(60000, 784).astype('float32') / 255
x_test = x_test.reshape(10000, 784).astype('float32') / 255

ตัวอย่างนี้แปลงข้อมูลอินพุตจาก int8 เป็น float32 คุณไม่ได้แคสต์ไปที่ float16 เนื่องจากการแบ่งตาม 255 อยู่บน CPU ซึ่งรันการทำงานของ float16 ช้ากว่าการดำเนินการ float32 ในกรณีนี้ ประสิทธิภาพแตกต่างกันเล็กน้อย แต่โดยทั่วไป คุณควรรันการคำนวณอินพุตใน float32 หากทำงานบน CPU เลเยอร์แรกของโมเดลจะส่งอินพุตไปที่ float16 เนื่องจากแต่ละเลเยอร์ส่งอินพุตแบบทศนิยมไปยัง dtype ของการคำนวณ

มีการดึงน้ำหนักเริ่มต้นของแบบจำลอง ซึ่งจะช่วยให้ฝึกตั้งแต่เริ่มต้นอีกครั้งด้วยการโหลดตุ้มน้ำหนัก

initial_weights = model.get_weights()

ฝึกโมเดลด้วย Model.fit

ต่อไป ฝึกโมเดล:

history = model.fit(x_train, y_train,
                    batch_size=8192,
                    epochs=5,
                    validation_split=0.2)
test_scores = model.evaluate(x_test, y_test, verbose=2)
print('Test loss:', test_scores[0])
print('Test accuracy:', test_scores[1])
Epoch 1/5
6/6 [==============================] - 2s 78ms/step - loss: 4.9609 - accuracy: 0.4132 - val_loss: 0.6643 - val_accuracy: 0.8437
Epoch 2/5
6/6 [==============================] - 0s 34ms/step - loss: 0.7752 - accuracy: 0.7789 - val_loss: 0.3098 - val_accuracy: 0.9175
Epoch 3/5
6/6 [==============================] - 0s 34ms/step - loss: 0.3620 - accuracy: 0.8848 - val_loss: 0.3149 - val_accuracy: 0.8969
Epoch 4/5
6/6 [==============================] - 0s 34ms/step - loss: 0.2998 - accuracy: 0.9066 - val_loss: 0.2988 - val_accuracy: 0.9068
Epoch 5/5
6/6 [==============================] - 0s 33ms/step - loss: 0.2298 - accuracy: 0.9285 - val_loss: 0.5062 - val_accuracy: 0.8414
313/313 - 0s - loss: 0.5163 - accuracy: 0.8392
Test loss: 0.5163048505783081
Test accuracy: 0.8392000198364258

สังเกตว่ารุ่นพิมพ์เวลาต่อขั้นตอนในบันทึก: ตัวอย่างเช่น "25ms/ขั้นตอน" ยุคแรกอาจช้าลงเนื่องจาก TensorFlow ใช้เวลาในการปรับโมเดลให้เหมาะสมที่สุด แต่หลังจากนั้น เวลาต่อขั้นตอนจะคงที่

หากคุณกำลังใช้งานคู่มือนี้ใน Colab คุณสามารถเปรียบเทียบประสิทธิภาพของความแม่นยำแบบผสมกับ float32 ได้ ในการดำเนินการดังกล่าว ให้เปลี่ยนนโยบายจาก mixed_float16 เป็น float32 ในส่วน "การตั้งค่านโยบาย dtype" จากนั้นรันเซลล์ทั้งหมดอีกครั้งจนถึงจุดนี้ สำหรับ GPU ที่มีความสามารถในการคำนวณ 7.X คุณควรเห็นเวลาต่อขั้นตอนเพิ่มขึ้นอย่างมาก ซึ่งบ่งชี้ว่าความแม่นยำแบบผสมทำให้โมเดลเร็วขึ้น ตรวจสอบให้แน่ใจว่าได้เปลี่ยนนโยบายกลับไปเป็น mixed_float16 และเรียกใช้เซลล์อีกครั้งก่อนที่จะดำเนินการตามคำแนะนำ

สำหรับ GPU ที่มีความสามารถในการคำนวณอย่างน้อย 8.0 (Ampere GPU ขึ้นไป) คุณอาจไม่เห็นการปรับปรุงประสิทธิภาพในโมเดลของเล่นในคู่มือนี้เมื่อใช้ความแม่นยำแบบผสมเมื่อเทียบกับ float32 นี่เป็นเพราะการใช้ TensorFloat-32 ซึ่งใช้คณิตศาสตร์ที่มีความแม่นยำต่ำกว่าโดยอัตโนมัติใน float32 ops บางอย่าง เช่น tf.linalg.matmul TensorFloat-32 ให้ข้อดีด้านประสิทธิภาพบางประการของความแม่นยำแบบผสมเมื่อใช้ float32 อย่างไรก็ตาม ในแบบจำลองการใช้งานจริง โดยทั่วไปแล้ว คุณจะยังคงเห็นการปรับปรุงประสิทธิภาพอย่างมีนัยสำคัญจากความแม่นยำแบบผสมเนื่องจากการประหยัดแบนด์วิดท์หน่วยความจำและ ops ที่ TensorFloat-32 ไม่รองรับ

หากใช้ความแม่นยำแบบผสมบน TPU คุณจะไม่เห็นประสิทธิภาพที่เพิ่มขึ้นมากนักเมื่อเทียบกับการใช้ความแม่นยำแบบผสมบน GPU โดยเฉพาะ GPU แบบพรีแอมแปร์ นี่เป็นเพราะ TPU ทำ ops บางอย่างใน bfloat16 ภายใต้ประทุนแม้จะมีนโยบาย dtype เริ่มต้นของ float32 ซึ่งคล้ายกับวิธีที่ Ampere GPUs ใช้ TensorFloat-32 เป็นค่าเริ่มต้น เมื่อเทียบกับ Ampere GPU โดยทั่วไปแล้ว TPU จะเห็นประสิทธิภาพที่เพิ่มขึ้นน้อยลงด้วยความแม่นยำแบบผสมในรุ่นจริง

สำหรับโมเดลในโลกแห่งความเป็นจริงหลายๆ รุ่น ความแม่นยำแบบผสมยังช่วยให้คุณเพิ่มขนาดแบทช์เป็นสองเท่าได้โดยไม่ต้องใช้หน่วยความจำไม่เพียงพอ เนื่องจากเทนเซอร์ float16 ใช้หน่วยความจำเพียงครึ่งเดียว อย่างไรก็ตาม สิ่งนี้ใช้ไม่ได้กับโมเดลของเล่นนี้ เนื่องจากคุณสามารถเรียกใช้โมเดลใน dtype ใดๆ ก็ได้ โดยที่แต่ละชุดประกอบด้วยชุดข้อมูล MNIST ทั้งหมดจำนวน 60,000 ภาพ

มาตราส่วนการสูญเสีย

มาตราส่วนการสูญเสียเป็นเทคนิคที่ tf.keras.Model.fit ดำเนินการโดยอัตโนมัติด้วยนโยบาย mixed_float16 เพื่อหลีกเลี่ยงตัวเลขที่น้อยเกินไป ส่วนนี้อธิบายว่าการวัดการสูญเสียคืออะไร และส่วนถัดไปจะอธิบายวิธีใช้งานกับลูปการฝึกแบบกำหนดเอง

อันเดอร์โฟลว์และโอเวอร์โฟลว์

ชนิดข้อมูล float16 มีช่วงไดนามิกที่แคบเมื่อเทียบกับ float32 ซึ่งหมายความว่าค่าที่สูงกว่า \(65504\) จะล้นไปยังค่าอนันต์ และค่าที่ต่ำกว่า \(6.0 \times 10^{-8}\) -placeholder2 จะล้นเหลือศูนย์ float32 และ bfloat16 มีช่วงไดนามิกที่สูงกว่ามาก ดังนั้นการล้นและอันเดอร์โฟลว์จึงไม่มีปัญหา

ตัวอย่างเช่น:

x = tf.constant(256, dtype='float16')
(x ** 2).numpy()  # Overflow
inf
x = tf.constant(1e-5, dtype='float16')
(x ** 2).numpy()  # Underflow
0.0

ในทางปฏิบัติ ล้นด้วย float16 ไม่ค่อยเกิดขึ้น นอกจากนี้ อันเดอร์โฟลว์ยังไม่ค่อยเกิดขึ้นในระหว่างการส่งต่อ อย่างไรก็ตาม ระหว่างการย้อนกลับ การไล่ระดับสีอาจต่ำกว่าศูนย์ การปรับขนาดการสูญเสียเป็นเทคนิคในการป้องกันการไหลล้นนี้

ภาพรวมสเกลการสูญเสีย

แนวคิดพื้นฐานของการสเกลการสูญเสียนั้นเรียบง่าย เพียงคูณการสูญเสียด้วยจำนวนมาก เช่น \(1024\)และคุณจะได้ค่า สเกลการสูญเสีย ซึ่งจะทำให้การไล่ระดับสีขยายตาม \(1024\) เช่นกัน ซึ่งช่วยลดโอกาสที่น้ำจะไหลน้อยเกินไป เมื่อคำนวณการไล่ระดับสีสุดท้ายแล้ว ให้หารด้วย \(1024\) เพื่อนำกลับไปเป็นค่าที่ถูกต้อง

รหัสเทียมสำหรับกระบวนการนี้คือ:

loss_scale = 1024
loss = model(inputs)
loss *= loss_scale
# Assume `grads` are float32. You do not want to divide float16 gradients.
grads = compute_gradient(loss, model.trainable_variables)
grads /= loss_scale

การเลือกระดับการสูญเสียอาจเป็นเรื่องยุ่งยาก หากสเกลการสูญเสียต่ำเกินไป การไล่ระดับสีอาจยังน้อยไปจนเหลือศูนย์ หากสูงเกินไป ปัญหาจะเกิดขึ้นตรงกันข้าม: การไล่ระดับสีอาจล้นจนอนันต์

ในการแก้ปัญหานี้ TensorFlow จะกำหนดระดับการสูญเสียแบบไดนามิก คุณจึงไม่ต้องเลือกด้วยตนเอง หากคุณใช้ tf.keras.Model.fit การปรับขนาดการสูญเสียจะทำเพื่อคุณ คุณจึงไม่ต้องดำเนินการใดๆ เพิ่มเติม หากคุณใช้การวนรอบการฝึกแบบกำหนดเอง คุณต้องใช้ wrapper ของเครื่องมือเพิ่มประสิทธิภาพพิเศษ tf.keras.mixed_precision.LossScaleOptimizer อย่างชัดเจนเพื่อใช้การปรับขนาดการสูญเสีย ซึ่งจะอธิบายไว้ในส่วนถัดไป

ฝึกโมเดลด้วยลูปการฝึกแบบกำหนดเอง

จนถึงตอนนี้ คุณได้ฝึกโมเดล Keras ด้วยความแม่นยำแบบผสมโดยใช้ tf.keras.Model.fit ถัดไป คุณจะใช้ความแม่นยำแบบผสมผสานกับลูปการฝึกแบบกำหนดเอง หากคุณไม่ทราบว่าวงการฝึกแบบกำหนดเองคืออะไร โปรดอ่าน คู่มือการฝึกอบรมแบบกำหนดเอง ก่อน

การรันลูปการฝึกแบบกำหนดเองด้วยความแม่นยำแบบผสมต้องมีการเปลี่ยนแปลงสองครั้งในการรันใน float32:

  1. สร้างแบบจำลองที่มีความแม่นยำแบบผสม (คุณเคยทำมาแล้ว)
  2. ใช้การปรับขนาดการสูญเสียอย่างชัดเจนหากใช้ mixed_float16

สำหรับขั้นตอนที่ (2) คุณจะใช้คลาส tf.keras.mixed_precision.LossScaleOptimizer ซึ่งจะรวมตัวเพิ่มประสิทธิภาพและใช้การปรับขนาดการสูญเสีย โดยค่าเริ่มต้น มันจะกำหนดระดับการสูญเสียแบบไดนามิก ดังนั้นคุณจึงไม่ต้องเลือก สร้าง LossScaleOptimizer ดังนี้

optimizer = keras.optimizers.RMSprop()
optimizer = mixed_precision.LossScaleOptimizer(optimizer)

ถ้าคุณต้องการ คุณสามารถเลือกมาตราส่วนการสูญเสียที่ชัดเจนหรือปรับแต่งพฤติกรรมการมาตราส่วนการสูญเสีย แต่ขอแนะนำอย่างยิ่งให้เก็บลักษณะการทำงานมาตราส่วนการสูญเสียที่เป็นค่าเริ่มต้นไว้ เนื่องจากจะพบว่าทำงานได้ดีกับแบบจำลองที่รู้จักทั้งหมด ดูเอกสารประกอบ tf.keras.mixed_precision.LossScaleOptimizer หากคุณต้องการปรับแต่งพฤติกรรมการปรับขนาดการสูญเสีย

ถัดไป กำหนดวัตถุการสูญเสียและ tf.data.Dataset s:

loss_object = tf.keras.losses.SparseCategoricalCrossentropy()
train_dataset = (tf.data.Dataset.from_tensor_slices((x_train, y_train))
                 .shuffle(10000).batch(8192))
test_dataset = tf.data.Dataset.from_tensor_slices((x_test, y_test)).batch(8192)

ถัดไป กำหนดฟังก์ชันขั้นตอนการฝึก คุณจะใช้วิธีการใหม่สองวิธีจากเครื่องมือเพิ่มประสิทธิภาพระดับการสูญเสียเพื่อปรับขนาดการสูญเสียและยกเลิกการปรับขนาดการไล่ระดับสี:

  • get_scaled_loss(loss) : คูณความสูญเสียด้วยสเกลการสูญเสีย
  • get_unscaled_gradients(gradients) : รับรายการการไล่ระดับแบบปรับขนาดเป็นอินพุต และแบ่งแต่ละรายการตามระดับการสูญเสียเพื่อยกเลิกการปรับขนาด

ต้องใช้ฟังก์ชันเหล่านี้เพื่อป้องกันการไหลเกินในการไล่ระดับ LossScaleOptimizer.apply_gradients จะใช้การไล่ระดับสีหากไม่มี Inf s หรือ NaN s นอกจากนี้ยังจะอัปเดตระดับการสูญเสียโดยลดลงครึ่งหนึ่งหากการไล่ระดับสีมี Inf s หรือ NaN และอาจเพิ่มขึ้นเป็นอย่างอื่น

@tf.function
def train_step(x, y):
  with tf.GradientTape() as tape:
    predictions = model(x)
    loss = loss_object(y, predictions)
    scaled_loss = optimizer.get_scaled_loss(loss)
  scaled_gradients = tape.gradient(scaled_loss, model.trainable_variables)
  gradients = optimizer.get_unscaled_gradients(scaled_gradients)
  optimizer.apply_gradients(zip(gradients, model.trainable_variables))
  return loss

LossScaleOptimizer มักจะข้ามสองสามขั้นตอนแรกเมื่อเริ่มการฝึก ระดับการสูญเสียเริ่มต้นสูงเพื่อให้สามารถกำหนดระดับการสูญเสียที่เหมาะสมได้อย่างรวดเร็ว หลังจากไม่กี่ขั้นตอน ระดับการสูญเสียจะคงที่และข้ามขั้นตอนน้อยมาก กระบวนการนี้เกิดขึ้นโดยอัตโนมัติและไม่ส่งผลต่อคุณภาพการฝึก

ตอนนี้ กำหนดขั้นตอนการทดสอบ:

@tf.function
def test_step(x):
  return model(x, training=False)

โหลดน้ำหนักเริ่มต้นของโมเดล เพื่อให้คุณสามารถฝึกใหม่ได้ตั้งแต่ต้น:

model.set_weights(initial_weights)

สุดท้าย ให้รันลูปการฝึกแบบกำหนดเอง:

for epoch in range(5):
  epoch_loss_avg = tf.keras.metrics.Mean()
  test_accuracy = tf.keras.metrics.SparseCategoricalAccuracy(
      name='test_accuracy')
  for x, y in train_dataset:
    loss = train_step(x, y)
    epoch_loss_avg(loss)
  for x, y in test_dataset:
    predictions = test_step(x)
    test_accuracy.update_state(y, predictions)
  print('Epoch {}: loss={}, test accuracy={}'.format(epoch, epoch_loss_avg.result(), test_accuracy.result()))
Epoch 0: loss=4.869325160980225, test accuracy=0.7221999764442444
Epoch 1: loss=0.4893573224544525, test accuracy=0.878000020980835
Epoch 2: loss=0.36011582612991333, test accuracy=0.9440000057220459
Epoch 3: loss=0.27391332387924194, test accuracy=0.9318000078201294
Epoch 4: loss=0.247697651386261, test accuracy=0.933899998664856

เคล็ดลับประสิทธิภาพของ GPU

ต่อไปนี้คือเคล็ดลับด้านประสิทธิภาพเมื่อใช้ความแม่นยำแบบผสมกับ GPU

การเพิ่มขนาดแบทช์ของคุณ

หากไม่ส่งผลต่อคุณภาพของโมเดล ให้ลองใช้ขนาดแบทช์เป็นสองเท่าเมื่อใช้ความแม่นยำแบบผสม เนื่องจากเมตริกซ์ float16 ใช้หน่วยความจำเพียงครึ่งเดียว จึงมักจะทำให้คุณสามารถเพิ่มขนาดแบทช์เป็นสองเท่าได้โดยไม่ต้องใช้หน่วยความจำไม่เพียงพอ การเพิ่มขนาดแบทช์มักจะเพิ่มปริมาณงานการฝึก กล่าวคือ องค์ประกอบการฝึกต่อวินาทีที่โมเดลของคุณสามารถรันได้

ตรวจสอบให้แน่ใจว่ามีการใช้ GPU Tensor Cores

ดังที่ได้กล่าวไว้ก่อนหน้านี้ NVIDIA GPUs สมัยใหม่ใช้หน่วยฮาร์ดแวร์พิเศษที่เรียกว่า Tensor Cores ซึ่งสามารถคูณเมทริกซ์ float16 ได้อย่างรวดเร็ว อย่างไรก็ตาม Tensor Cores ต้องการมิติข้อมูลเทนเซอร์บางค่าที่เป็นพหุคูณของ 8 ในตัวอย่างด้านล่าง อาร์กิวเมนต์เป็นตัวหนาก็ต่อเมื่อจำเป็นต้องเป็นทวีคูณของ 8 เท่านั้นจึงจะสามารถใช้ Tensor Cores ได้

  • tf.keras.layers.Dense( หน่วย=64 )
  • tf.keras.layers.Conv2d ( ตัวกรอง=48 , kernel_size=7, stride=3)
    • และในทำนองเดียวกันสำหรับเลเยอร์ที่โค้งงออื่นๆ เช่น tf.keras.layers.Conv3d
  • tf.keras.layers.LSTM( หน่วย=64 )
    • และคล้ายกันสำหรับ RNN อื่นๆ เช่น tf.keras.layers.GRU
  • tf.keras.Model.fit(ยุค=2, batch_size=128 )

คุณควรลองใช้ Tensor Cores เมื่อเป็นไปได้ หากคุณต้องการเรียนรู้เพิ่มเติม คู่มือประสิทธิภาพการเรียนรู้เชิงลึกของ NVIDIA จะอธิบายข้อกำหนดที่แน่นอนสำหรับการใช้ Tensor Core รวมถึงข้อมูลประสิทธิภาพอื่นๆ ที่เกี่ยวข้องกับ Tensor Core

XLA

XLA เป็นคอมไพเลอร์ที่สามารถเพิ่มประสิทธิภาพความแม่นยำแบบผสมเพิ่มเติมได้ เช่นเดียวกับประสิทธิภาพ float32 ในระดับที่น้อยกว่า โปรดดูรายละเอียดใน คู่มือ XLA

เคล็ดลับประสิทธิภาพ Cloud TPU

เช่นเดียวกับ GPU คุณควรลองเพิ่มขนาดแบทช์เป็นสองเท่าเมื่อใช้ Cloud TPU เนื่องจากเทนเซอร์ bfloat16 ใช้หน่วยความจำเพียงครึ่งเดียว การเพิ่มขนาดแบทช์เป็นสองเท่าอาจเพิ่มปริมาณการฝึกอบรม

TPU ไม่ต้องการการปรับแต่งเฉพาะที่มีความแม่นยำแบบผสมอื่นๆ เพื่อให้ได้ประสิทธิภาพสูงสุด พวกเขาต้องการใช้ XLA แล้ว TPU ได้ประโยชน์จากการมีมิติบางอย่างที่เป็นทวีคูณของ \(128\)แต่สิ่งนี้ใช้ได้กับประเภท float32 เช่นเดียวกับความแม่นยำแบบผสม ตรวจสอบ คู่มือประสิทธิภาพ Cloud TPU สำหรับเคล็ดลับประสิทธิภาพ TPU ทั่วไป ซึ่งใช้กับเมตริกซ์ที่มีความแม่นยำแบบผสมและเมตริกซ์ float32

สรุป

  • คุณควรใช้ความแม่นยำแบบผสมหากคุณใช้ TPU หรือ NVIDIA GPU ที่มีความสามารถในการคำนวณเป็นอย่างน้อย 7.0 เนื่องจากจะปรับปรุงประสิทธิภาพได้มากถึง 3 เท่า
  • คุณสามารถใช้ความแม่นยำแบบผสมกับบรรทัดต่อไปนี้:

    # On TPUs, use 'mixed_bfloat16' instead
    mixed_precision.set_global_policy('mixed_float16')
    
  • หากรุ่นของคุณลงท้ายด้วย softmax ตรวจสอบให้แน่ใจว่าเป็น float32 และไม่ว่าโมเดลของคุณจะลงท้ายด้วยอะไร ตรวจสอบให้แน่ใจว่าเอาต์พุตเป็น float32

  • หากคุณใช้การวนรอบการฝึกแบบกำหนดเองกับ mixed_float16 นอกเหนือจากบรรทัดด้านบนแล้ว คุณต้องรวมออปติไมเซอร์ของคุณด้วย tf.keras.mixed_precision.LossScaleOptimizer จากนั้นเรียก optimizer.get_scaled_loss .get_scaled_loss เพื่อปรับขนาดการสูญเสีย และ optimizer.get_unscaled_gradients .get_unscaled_gradients เพื่อยกเลิกการปรับขนาดการไล่ระดับสี

  • เพิ่มขนาดชุดการฝึกเป็นสองเท่าหากไม่ลดความแม่นยำในการประเมิน

  • สำหรับ GPU ตรวจสอบให้แน่ใจว่ามิติเทนเซอร์ส่วนใหญ่เป็นตัวคูณของ \(8\) เพื่อเพิ่มประสิทธิภาพสูงสุด

สำหรับตัวอย่างเพิ่มเติมของความแม่นยำแบบผสมโดยใช้ tf.keras.mixed_precision API ให้ตรวจสอบที่ เก็บโมเดลอย่างเป็นทางการ โมเดลที่เป็นทางการส่วนใหญ่ เช่น ResNet และ Transformer จะทำงานโดยใช้ความแม่นยำแบบผสมโดยผ่าน --dtype=fp16